标题用PHP开发健壮的代码(一):高屋建瓴的介绍
日期:    作者:爱好者   来源:php之家
文章打印自:
访问文章完全地址:
头部广告
用PHP开发健壮的代码(一):高屋建瓴的介绍
用 PHP 开发健壮的代码 系列文章是关于解决大中型应用程序中的实际问题的。这一系列文章主要侧重于 PHP 4 中可用的新功能,重点介绍了大量使开发工作更容易的技巧和窍门。在这一系列文章中,您将发现许多要学习的示例和技术,还附带了大量样本代码。在这第一篇文章中,PHP 高手 Amol Hatwar 从更高的角度介绍了如何为中到大型 Web 应用程序设计和编写无错误、可维护的代码。

如果您是一名构建 Web 应用程序的开发者并且需要速度、功能和平台独立性(platform-independence),那么 PHP 将适合您。而且 PHP 是免费的,易于学习和部署。这些是使 PHP 如此受欢迎的最大优点。但这些优点也可以变成缺点。由于 PHP 易于使用,所以开发者在本应规划和设计的时候,他们经常先把代码硬塞到编辑器中。而且,在 PHP 中,解决问题的方法不止一种,比较容易犯那种惨痛的错误,这种错误过后难以订正。

在这一系列文章中,您将学会如何避免许多错误。如果您一步不落地坚持到底,您会发现自己尝试一两次就能编写无错误的代码了,对此不要感到奇怪。我还会指出 PHP 4 中可用的新功能,它们使开发工作更容易。我要介绍的大多数示例都是用来处理诸如脚本配置和安装、文件处理以及数据库使用之类的实际问题。即使您对这一切都不熟悉,您也会发现理解起来很容易。不过,我假设您对 PHP 有初步的了解。如果您需要重新温习,您会发现本文结尾处所列出的参考资料会对您有所帮助。(请参阅参考资料。)

奠定健壮的基础
用 PHP 编写代码与用类似 C 的语言编写代码非常相似。由于它们句法上类似,所以还会导致类似的代码维护问题。当必须开发大型应用程序时,您可能要编写大量代码。随着时间的流逝,这些代码可能变得难以管理,错误很快就会乘虚而入。无论您从哪里听到这种说法,都不要相信 — 至少不能全信。但是,更重要的事实是如果您发现自己维护代码过于频繁,那么首先您的应用程序的设计可能是很糟糕的。

正确地设计代码
您最初做出的选择会影响您以后编写代码时的自由度。这使得正确的设计成为一个重要的先决条件。虽然当您解决一个微不足道的问题时设计会成为一个额外的仪式,但是您至少必须知道就是应该这样做的。许多人把设计和规划看作额外的开销。但是以糟糕的设计开始,或者根本就没有设计,结果总是会造成草率的代码。请记住,再巧妙的编码也不能弥补糟糕的设计。虽然如何设计应用程序超出了这个系列文章的范畴,但我还是会提示一些设计 Web 应用程序时应该牢记的问题。

分割和征服
通过一个个较小的松散耦合的部件来设计和编写大型应用程序总是更可取的。这样,每个部件都是可维护的。例如,一个内容管理系统(Content Management System,CMS)可能分布在许多诸如用户认证、显示、内容解析和查看统计信息等较小的模块上。而且,如果您的模块足够通用的话,您可以在您开发的其它应用程序中重用代码。程序员经常这么做,但他们仍然抱怨。至于编码的技巧就是让每个模块具有它绝对需要的功能并且到此为止。赋予一个模块的功能要不多不少,恰到好处。

绝对不要在页面中考虑
如果您对用 PHP 将您的 HTML 页面变得更动态些感兴趣,这部分将适合您。当您想在每个页面上显示日期和时间以使其看上去是最新的时候,通常是这个习惯开始的时候。每个 HTML 页面都变成一个只有有限几行的小 PHP 脚本,日期函数隐藏在某个地方。如果这是您想要的,您必须认可这是使该作业实现的最简单的方式。但是,想象一下您必须做些什么变动才能改变页面上呈现的日期格式。您将不得不更改每个页面内的代码。

显然,有更好的方式来达到这个目的。我最喜欢的方式是使用配置文件并在配置文件中定义一个常量,这个常量保留 date() 函数的格式字符串。然后,您就可以在需要的地方使用 date() 函数了。每个页面仍以脚本结束,但是您要彻底地把那些您必须对单行代码作更改的地方降到最少。

 

如上图所示,您在所有页面上都能看到日期格式的更改。这里的想法就是要避免复制代码以及硬编码。在编写大型应用程序时,请牢记这一点。当您避免了复制代码时,调试和维护就变得更加容易了。

减少客户机端要求
Web 页面和应用程序的类型已经向多方向发展了。首先是图像和图像映射,然后是实现很酷的动画的 Java applet 和客户机端脚本。现在是 Flash。有影响是好事情,但是您必须记住 Web 背后的整体思想是使任何需要信息的人都能随时访问这些信息。如果您使用并非所有浏览器和平台都支持的技术,就会拒绝人们访问您的信息。您永远不知道不能访问您 Web 站点的人是否本来可以成为您的下一个客户!

您的应用程序设计应该尽可能地将网络流量降到最低。我们经常看到许多网站访问量过多并且只给浏览器一类仅能使其运行的 cookie。篡改大量 cookie 不仅消耗带宽,而且还使得许多方面难以管理。根据经验,如果您的应用程序发送超过 40 KB 的数据或者如果您的页面需要多于 5 秒的时间来装入,那就该从头设计应用程序了。各处的小调整不会持续太久。如果您到处看看,就会发现最受欢迎的、访问人数最多的站点都是非常简单的。

您还应该考虑下一波移动设备以及它们访问您的应用程序所用的连接。最好的做法就是,您的应用程序必须根据请求内容的客户机对内容进行调整。Leon Atkinson 在他的 Core PHPProgramming 一书的第 720 页中说到,“我们可以设法将 HTML 文档的大小控制得较小,并且我们可以设法避免诸如嵌套表(nested table)这样的复杂的 HTML,但是我们不能对每个人的 28.8 调制解调器都进行升级。”

把代码、内容和显示分开
HTML 是显示内容的标记语言,PHP 是嵌入 HTML 的脚本语言。这确实使简单的任务变得容易 — 例如,以日期为例。不过,当您要实现复杂的要求时,将 PHP 嵌入 HTML 使代码的简单性尽失。尽可能地把代码、内容和显示分开,这很好。考虑一下您正在阅读的这个文档。这个文档开始被创建为一个 XML 文件。HTML 和 PDF 版本是用样式表自动生成的。代码(向您显示该页面的应用程序)、内容(XML 文档)和显示(样式表(style sheet))是不同的。

正象将核心功能封装在模块中以及避免代码复制很重要一样,用单独的内容源代码以及按照要求的方式显示内容也很重要。您根据客户机和连接速度定制页面的灵活性也增加了。同时使程序员、设计师和作者彼此独立工作 — 如果您正在处理一个大型项目,这是一件好事。

不要害怕抛弃设计
无论别人告诉您什么,实践才是学习应用程序设计的最佳方式。如果您刚刚起步,可能会犯许多错误 — 这就是学习方法。糟糕的设计应该被抛弃。这就是您必须保持代码、内容和显示松散耦合的原因 — 抛弃糟糕的设计成为减轻痛苦的手段。当您丢弃陈旧的代码,以更好的代码取而代之的时候,您可以保留内容和显示。

现在,回到我所承诺的问题上,使您的代码健壮起来。您必须一直记住您的代码将为其他人的内容和显示提供力量。如果您的代码不能胜任的话,其他部门再多惊人的努力也不能弥补这个缺陷。

编写健壮的代码
假定您的代码要求不变,您将不会明显地发现更改代码的需要。除了偶尔需要最优化和改进之外,您的代码应该像加了润滑油的机器一样运行。

听上去很困难?事实并非如此。坦白地说,编写健壮的代码并不需要天才。您只需要在拿不准的时候问自己一些适当的问题,这样就不会偏离正轨:

保护您的代码
任何使大量用户满意的系统都必须是安全的。尽管 PHP 本身不易受到黑帽(black-hat)黑客的攻击,但是您不要太肯定。在版本 4.2.2 之前的 PHP 4 有严重的安全性缺陷。要一直确保在有适当加密的网络上存储或传送敏感数据。这对于处理业务、存储信息(如信用卡号码)以及传输机密数据的应用程序来说更重要。

现在,很难信任用户提交的数据。要确保对数据进行了验证并确保数据在使用前是清白的。请牢记,将您的 Web 应用程序放到因特网上就是向巨大的网络公布了您的系统、软件、数据以及业务。

确保您的代码一直安全地运行。

保持代码简单
您的代码应该是易于理解、可读性好且文档良好的。为了减少您熟悉自己或其他人的代码所需的时间,请在工程内一直使用公共命名和编码约定。请投入时间以确保在需要维护代码时这些方面会有所回报。

您最好在编程时为代码建立文档。能为您解析所有的脚本并创建看起来整洁的 HTML 格式的文档的工具并不存在。如果您改变了代码的行为,就要相应地改变文档。如果代码的文档并没有实际记录什么东西,那么拥有这样的文档是没用的。

确保您的代码是备有文档的、简单的并且易于理解的。从长远来看这样会有所帮助。

使代码是平台独立的
您必须解决的另一个问题是平台独立性。当然,为 Windows 上的 PHP 编写的脚本将对任何其它平台上的 PHP 起作用:PHP 就是这样设计的。不过,您仍然需要小心比较小的不一致。例如,换行字符在 Windows 和 UNIX 中就以不同的方式表示。

您在访问资源(如位于 PHP 外部的数据库)时还必须使用抽取。比方说您的应用程序用 MySQL 作为数据库服务器来削减成本。如果您决定以后拥有一个功能更丰富的数据库,您必须更改应用程序中的代码。对代码进行重大更改总是一个苦差事而且是一个易出错的过程。请使用抽取来隔离对易于更改的部分的改变。您不必重新编写整个应用程序。

确保您编写的是平台独立的代码。这使您的应用程序具有更好的适应性和可伸缩性。

为速度构建
最后一个值得处理的因素是速度。当您的脚本从数据库中拖拖拉拉地选择大约 300 个条目并显示一个页面时,没有人愿意一直等下去。将 20 个结果放在 15 个不同的页面(这些页面在用户的浏览器上快速移动和装入)上通常是一个较好的主意。用户把更快的响应时间理解为速度。另一个易犯的错误是每次用户访问页面时都动态创建页面。这的确能确保您的站点总是最新,但是当用户数量增加时,PHP 就不能容许这样做了。您应该高速缓存那些使用频繁的页面。高速缓存使您的应用程序速度更快并且减少了服务器上的负载。

确保您的代码快速运行。没人喜欢等待。

总结
在这个系列的开篇文章中,您了解了编写健壮的代码要实际做些什么事。如果您认真学习的话,用 PHP 开发大型应用程序一点儿都不难。事实上,许多用 PHP 编写的应用程序已经轻松开发出来了。同时,意识到您容易掉入陷阱中是很重要的。只要您进行了规划、把事情分解成许多小一些的任务并且正确实现它们 — 几乎没有别的什么会阻碍您了。

在下一篇文章中,您将学会如何高效率地使用变量和函数。我将额外向您展示如何用 PHP 中的变量和函数变一些戏法。我们在这篇文章中规划了我们要讨论的主要内容。在下一篇文章之后,我们将逐个讨论这些内容 — 可能甚至会跳跃着讲述。到时再见。

参考资料

用PHP开发健壮的代码(一):高屋建瓴的介绍

“用 PHP 开发健壮的代码”是关于解决大中型应用程序中的实际问题的系列文章。在本文中,PHP 老手 Amol Hatwar 讨论了如何有效地使用变量。他还演示了如何通过使用 PHP 中可变的变量名来构造配置文件解析器,以便简化脚本配置。
在我的前一篇文章中,我研究了在规划、设计甚至编写代码期间必须考虑的一些因素。在本文中,您将真正接触到实际代码,并可以看到实际运行中的一些东西。如果您还没有看过前一篇文章,那么最好现在就看一看。


变量与函数是任何计算机语言必不可少的要素。有了变量,您可以将数据抽象化;有了函数,您可以将几行代码抽象化。正如 Bruce Eckel 在他的书籍《C++ 编程思想》中所说的那样,所有编程语言都提供抽象。汇编语言是对底层机器的小抽象。随后的许多所谓的命令式语言(如 Fortran、BASIC 和 C)是对汇编语言的抽象。

编程语言提供的抽象的种类和质量直接关系到您所能解决的问题的复杂程度。理解 PHP 如何处理变量和函数,将有助于您有效地使用它们。


就象我在前一篇文章中提到的那样,命名约定和编码约定是重要的。无论您使用什么命名约定,请记住要在项目中严格遵守它。如果您使用应用得最广泛的命名约定,那么您的代码将被更多的人所接受。

对变量进行命名时,在包括脚本时要特别注意不要覆盖正在使用的变量。在大型应用程序中,当增加新的功能时,这是常见的错误根源。防止这一问题的最佳办法就是使用前缀。把变量所在模块的名称缩写作为前缀来使用。例如,如果一个处理投票的模块中有一个保存用户标识的变量,那么您可以将该变量命名为 $poll_userID 或 $pollUserID。


PHP 是解释型语言。这有许多好处,很快您将学习利用其中的一些。第一个很明显的好处是:它使您省掉了设计-编码-编译-测试周期 — 您在编辑器中编写的任何代码都立即可使用。然而,最重要的好处是您不用担心变量的类型以及如何在内存中管理这些变量。所有分配给脚本的内存在执行完脚本后都由 PHP 自动收回。此外,可以对变量执行许多操作而不必知道变量的类型。清单 1 中的代码在 PHP 中工作十分正常,但在 C 和 Java 语言中会抛出一大堆错误消息:



<?php
$myStr = 789696; // An integer.
$myVar = 2; // Another integer.
$myStr = "This is my favorite band: "; // Strings are more fun.
$myStr = $myStr . "U" . $myVar; // Doing this is OK, too.
echo "$myVar\n";
?>


安装完 PHP 后,如要运行运行代码,可首先将该代码保存为一个 .php 文件,再将该文件放置在 Web 服务器上,然后将浏览器指向该文件。更好的办法是安装 PHP 的 CGI 版本。然后,通过在 shell 或命令提示符下输入以下命令,并用包含您的脚本的文件名替代 script-name,这样就可以运行该脚本了。

path-to-php/php script-name



该代码能够正常工作,因为 PHP 是类型宽松的语言。用通俗易懂的英语,可以不考虑变量类型,可以把字符串赋值给整数,以及毫不费力地用较大的字符串替代较小的字符串。这在象 C 这样的语言中是不可能的事情。在内部,PHP 将变量所拥有的数据与类型分开存储。类型存储在单独的表中。每当出现包含不同类型的表达式时,PHP 自动确定程序员想要做什么,接着更改表中的类型,然后自动对表达式求值。


不用担心类型固然很好,但有时那也会使您陷入真正的麻烦。怎么回事呢?这里有一个实际的示例:我常常必须把在基于 Windows 的 PC 上创建的内容移到 Linux 系统,以便能在 Web 上使用它们。基于 Windows 的文件系统在处理文件名时是不区分大小写的。文件名 DefParser.php 和 defparser.php 指向 Windows 上的同一文件。在 Linux 操作系统上,它们指向不同的文件。您可能提倡文件名要么全用大写,要么全用小写,但最好的做法应该是使大小写保持不变。


假设您想要一个函数,它能在不考虑大小写的情况下检查给定文件是否存在于某个目录中。首先,将这个任务分解成一些简单的步骤。分解代码可能听起来有些可笑,但它确实有助于您在编写代码时将主要精力放在这段代码上。另外,在纸上重写步骤始终比重写代码容易得多:

获取源目录中的所有文件名
过滤掉 . 和 .. 目录
检查目标文件是否存在于该目录中
如果文件存在,则获取具有正确大小写的文件名
如果名称不匹配,则返回 false
要读取目录的内容,需要使用 readdir() 函数。可以在 PHP 手册(请参阅参考资料)中获取有关该函数的更多细节。至于现在,只要知道:readdir() 在每次调用时会逐个返回给定目录中所有文件的名称。在列出了所有的文件名后,它返回 false。您将使用一个循环,该循环在 readdir() 返回 false 时终止。

但这样就够了吗?请记住,PHP 是类型宽松的语言,这意味着会将整型值 0 与 false 视为相同(甚至 C 也把 0 和布尔值 false 视为等价)。问题不是该代码是否能正常工作;想象一下,如果文件的名称是 0 会如何!该脚本会过早终止。可以使用以下脚本(清单 2)来确定 0 与布尔值 false 的等价性:



<?php
$file_name = 0;
if (0 == $file_name ) {
echo "The code is in trouble ...\n"; // This text prints on the screen.
}

else {
echo "Phew ... The code is safe"; // This text never prints.
}
?>



那么您可以做什么呢?您知道 PHP 会在内部存储类型,而如果能够访问这些类型的话,问题就解决了。布尔值 false 和整型值 0 明显是不同的。

PHP 有一个 gettype() 函数,但让我们在这里选择更简单的方法。您可以使用 === 运算符(是的,有三个等号)。不同之处在于该运算符同时比较数据的值和类型。如果您对此觉得有些疑惑,PHP 还有 !== 运算符。只有 PHP 4 中才有这些新型运算符和 gettype() 函数。清单 3 显示了解决该问题的完整代码:



<?php
/* This is the function where the action takes place */
function chk_file_name( $name, $path="." ) {
$fileList = get_file_list($path);
foreach ($fileList as $file) {
if (eregi($name, $file)) {
return $file;
}
}
return false;
}

/* Return the list of files in a given directory in an array.
Uses the current directory as default. */
function get_file_list($dirName=".") {
$list = array();
$handle = opendir($dirName);
while (false !== ($file = readdir($handle))) {

/* Omit the '.' and the '..' directories. */
if ((".."== $file) || ("."== $file)) continue;
array_push($list, $file);
}

closedir($handle);
return $list;
}

?>




我不打算对清单 3 中各个函数的功能加以说明,相反,我鼓励您查阅 PHP 手册(请参阅参考资料)。当您使用不熟悉的函数时,假设的参数与返回值的类型会是另一个错误根源。我没有对 PHP 中的内置函数加以说明,而是打算说明一些不太一目了然的事情。

当终止条件中涉及不同的变量类型时,通过使用 === 和 !== 运算符进行强类型检查是很重要的。

由各部分组成的代码
我本来可以将整个脚本编写为一个函数,但这里我却把代码分割成两个函数。还记得前一篇文章中的“分而治之”规则吗?我这么做正是因为每个函数所起的作用不同。如果您用其它脚本获取某个目录的内容,那么现在就可以使用方便的实现。我希望您考虑一些事情:想象一下将整个脚本作为一个函数来实现,然后想象调试、测试和重用代码所需的工作。

正确使用循环
现在看看 foreach 循环,想想为什么不用 for 循环?使用 for 循环要求您知道数组中项的数目 — 需要一个额外的步骤。此外,在处理 PHP 数组时,有可能超出数组边界。也就是说,在数组只有 10 个元素时,试图访问它的第 15 个元素。PHP 的确会给出一个小警告,但据我所知,在一些情况下,当反复运行某个脚本时,CPU 活动率会突然上升到 100% 而服务器性能则连续下降。我建议您尽可能地避免使用 for 循环。

断言性的 if
最后,我希望您研究一下那个在 get_file_list() 函数中用于忽略 . 和 .. 目录的较大的 if 条件。显然,我可以采用传统的方法,根据常数来检查变量。但在我自己的许多编码昏招中,我经常会遗漏等号并且在以后找不到哪里出了问题。当然,PHP 不会报错,因为它认为我想进行赋值而不是比较。当您根据变量来比较常数并且又遗漏了一个等号时,PHP 会抛出错误消息。


现在来讨论一些奇妙的事情。作为新手的开发人员认为,使用可变变量来完成任务是一种令人费解的方法,所以常常回避它。实际上,很容易理解和使用可变变量。它们已经不止一次地帮我摆脱困境,而且它们是一种重要的语言元素。事实上,在有些情况下,使用可变变量在所难免。很快我将研究一种此类现实情况,但首先让我们看看可变变量到底是什么。让我们先尝试一下清单 4 中的代码:



<?php
$myStr = "I";
$$myStr = "am";
$$$myStr = "great.";

// These are new variables.
echo "$myStr ";
echo "$I ";
echo "$am\n";

// Now for the moment of truth ...";

$am = "exaggerating.";

// Does it work the other way?
echo "$myStr ";
echo "${$myStr} ";
echo "${${$myStr}}\n ";
?>



首先,清单 4 中的代码声明了名为 $myStr 的变量,并将字符串 I 赋给它。接下来的语句定义了另一个变量。但这次,变量的名称是 $myStr 中的数据。$$myStr 是一种告诉 PHP 产生另一个变量的方法,其意思是“我想要一个变量,可以在变量 $myStr 中找到这个变量的名称”。当然,为做到这一点,必须定义 $myStr。所以,现在您有一个名为 I 的变量,并用字符串 am 给它赋值。接下来的语句做了同样的事情,并将字符串 great 赋给名为 am 的变量。而 am 只是变量 I 中的数据。

那澄清了一些事,但 echo 语句中那些奇怪的花括号是怎么回事呢?那就是您打印可变变量的方法。如果您省略花括号,那么 PHP 在打印时会将美元符号($)附加到变量的内容上。这些花括号告诉 PHP 首先对它们里面的变量求值。

试着这样考虑:变量可以做什么?简单地说,它们抽象或表示数据,这些数据可以变化,而它们的名称保持不变。可变变量做的是同样的事情;它们抽象数据。但在本例中,它们包含的数据实际上是另一个变量的名称。

我在清单 4 中所给的例子是为了让您熟悉可变变量。可变变量名的实际功能来自这样的事实:您可以在运行时动态地生成可变的变量名。您将在构造一个配置文件解析器时用到这一特性。


按照我的经验,在配置应用程序以使其运行期间,用 PHP 编辑配置文件时,用户常有所抱怨。配置只不过是全局变量的一个列表,应用程序中的其它脚本依靠它来查找文件、URL 和其它控制应用程序行为的设置。一个遗漏的美元符号、分号或一段未封闭的注释常常会破坏整个代码。有什么能帮助用户呢?

假设您给用户一个文件,让用户使用由等号分开的简单的名称-值对来编辑该文件。配置文件类似于清单 5。以 # 开头的行的文本被作为注释处理。



# This is sample a configuration file.
admin_fname = Amol
admin_lname = Hatwar
admin_email = amolhatwar@consultant.com
admin_login = admin
admin_pass = secretstring
# File Ends



意思清楚吗?是的,的确如此……既然可以用 PHP 解析文件,为什么让用户编辑配置文件呢?事实上,这是人们非常期望的。请记住,您的应用程序必须在对用户隐藏所有复杂性的同时,仍然让他知道他能控制该应用程序。

您可以编写负责解析工作的函数,这样您可以在任何地方使用它而不用做任何修改。让我们将该任务分为一些更简单的步骤:

逐行地读取文件
丢弃一行中 # 号字符后的所有内容
以等号为界,将一行分为两个字符串,并丢弃等号
除去字符串中的额外空格
相应声明变量
要编写最后一步,只有使用可变变量才行。清单 6 显示了代码:



<?php
/* conf_parser.php */

/* Give the filename with path info whenever possible. */
function conf_parse($file_name) {

// @ in front makes the function quiet. Error messages are not printed.
$fp = @fopen($file_name, "r") or die("Cannot open $file_name");

while ($conf_line = @fgets($fp, 1024)) {
$line = ereg_replace("#.*$", "", $line); // Do stripping after hashes.
if ($line == "") continue; // Drop blank lines resulting from the previous step.
list($name, $value) = explode ('=', $line); // Drop '=' and split.
$name = trim($name); // Strip spaces.
$$name= trim($value); // Define the said variable.
}
fclose($fp) or die("Can't close file $file_name");
}
?>



用正则表达式除去 # 号。尽管这里的表达式很简单,但要知道复杂的正则表达式会消耗大量的 CPU 时间。此外,为每页反复地解析配置文件不是一个好的决策。更好的选择是:使用变量或定义语句将已解析的输出存储为 PHP 脚本。我倾向于使用 define() 函数进行定义,因为一旦设置了值就不能在运行时更改它。可以在参考资料中找到一个能够根据您的需要加以修改的实现。


既然知道了如何有效地使用变量,那么您可以着手编写一些较大的程序了。在本系列的下一篇文章中,我将研究函数和 API 设计。在下次见面以前,希望您编程愉快!



下载 def_parser.zip 文件,其中包含配置文件解析器的实现,您可以将该解析器写的文件包括在您的脚本中。


请访问 PHPBuilder.com 和 Developer Shed,以获取更多有关在 PHP 中使用可变变量名称的示例。


请阅读了解 Free Energy,这是一个对 Web 应用程序进行编码的简单方法。


下载或在线查阅 PHP 手册。


参加“Writing efficient PHP”教程,学习如何编写有效的 PHP 代码(developerWorks,2002 年 7 月)。


请阅读本系列文章中的第一篇“奠定基础”,了解有关设计和规划 Web 应用程序的要点(developerWorks,2002 年 8 月)。

amolhatwar@consultant.com 与 Amol 联系。

用PHP开发健壮的代码(一):高屋建瓴的介绍

在本系列文章(有关如何在实际情况下开发有效的 PHP 代码)的第 3 部分中,Amol Hatwar 讨论了如何构建最有效的功能型函数,使用这些函数不会牺牲太多性能或可管理性。作者重点阐述了如何编写可重用函数,并介绍了如何避免与该任务相关的一些最常见问题。
欢迎回来。在本系列文章的第 1 部分中,我讨论了一些基本的 PHP 设计规则,并介绍了如何编写安全、简单、与平台无关且快速的代码。在第 2 部分中,我介绍了变量,并讨论了它们在 PHP 编码中的用法 — 好的和坏的实践。

在本文中,您将了解如何在 PHP 中明智地使用函数。在每一种高级编程语言中,程序员都可以定义函数,PHP 也不例外。唯一的区别在于,您不必担心函数的返回类型。
函数可用于:

将几行代码封装成一条语句。

简化代码。

最重要的是,将应用程序作为更小的应用程序相互协调的产物。
对于从编译语言(如 C/C++)转到 PHP 的开发人员来说,PHP 的性能级别是令人吃惊的。在使用 CPU 和内存资源方面,用户定义的函数非常昂贵。这主要是因为 PHP 是解释型和松散类型的。
有些开发人员仅仅因为不喜欢函数的名称就把他们使用的每个函数都包装起来,而另一些开发人员却根本不喜欢使用包装。

包装现有的 PHP 函数而不添加或补充现有的功能,是完全不能接受的。除了会增加大小和执行时间外,这样的重命名函数有时可能会带来管理上的恶梦。

代码中的内联函数会导致莫名其妙的代码,甚至是更大的管理灾难。这样做的唯一好处可能就是得到一个更快的代码。

更明智的方法是,仅在需要多次使用代码,并且对于您希望实现的任务没有可用的内置 PHP 函数时才定义函数。您可以选择重命名或仅当需要时才有限制地使用。

图 1 中的图表粗略地显示了可管理性和速度与使用的函数数量之间的相互关系。(在此我没标明单位,因为数字取决于个体和团队的能力;这一关系是重要的可视数据。)

正如我在本系列文章的第 2 部分(请参阅参考资料)中提到的,要获得有效的代码管理,始终都使用公共的命名约定是必不可少的。

其它两个需要考虑的实践是:

选择的名称应当能很好地提示函数的功能。
使用表明包或模块的前缀。
假定您有一个名为 user 的模块,它包含用户管理函数,那么对于检查用户当前是否在线的函数而言,诸如 usr_is_online() 和 usrIsOnline() 这样的函数名称都是上佳之选。

将上面的名称与 is_online_checker() 这样的函数名称相比较。得到的结论是,使用动词优于使用名词,因为函数始终都会做点什么。
很有可能您将使用已经构造的函数。即使情形并非如此,您可能也希望最大化代码的使用范围。要做到这一点,您和其他开发人员应当继续开发易于使用的函数。没人喜欢使用那些所带的参数既晦涩又难于理解的函数,因此请编写易于使用的函数。

选择一个能够说明函数用途的名称(并减少函数使用的参数数量)是确保易于使用的一个好方法。参数数量的幻数是什么呢?依我看来,超过三个参数就会使函数难以记忆。使用大量参数的复杂函数几乎都能被拆分成多个更简单的函数。

没人喜欢使用凑合的函数。
假定您希望在将 HTML 文档放到浏览器之前设置文档的标题。标题就是 <head>...</head> 标记之间的所有内容。

假定您希望设置 title 和 meta 标记。不使用 setHeader(title, name, value) 函数执行所有工作,而分别使用 setTitle(title) 和 setMeta(name, value) 完成各项工作是一个更佳的解决方案。该方案相互独立地设置 title 和 meta标记。

进一步考虑,标题可以只包含一个 title 标记,但它可以包含多个 meta 标记。如果需要设置多个 meta 标记,则代码必须多次调用 setMeta()。在这种情况下,更佳的解决方案是将带有名称-值对的两维数组传递给 setMeta(),并且让函数循环执行该数组 — 同时执行所有操作。

一般来说,象这样的同时的函数更可取。用函数需要处理的所有数据一次性调用函数,始终优于多次调用函数,并以增量的方式为其提供数据。编写函数时的主要思想是,尽量减少从其它代码对其的调用。

据此,setHeader() 解决方案实在不是好方法。显而易见,我们可以将 setHeader() 重构成 setHeader(title, array),但是也必须考虑到我们失去了相互独立地设置 title 和 meta 标记的能力。

此外,在实际环境中,标题可能包含多个标记,而不只是 title 和 meta 标记。如果需要添加更多标记,您必须更改 setHeader(),并且改变依赖于它的所有其它代码。在后一种情形下,只需多编写一个函数。

下面的等式适用于所有编程语言:
便于记忆的名称 + 清晰的参数 + 速度和效率 = 在所有编程语言中都适用的优质函数

函数很少是独自存在的。它们与其它函数共同起作用,交换和处理数据以完成任务。编写可与相同组或模块中的其它函数良好协作的函数很重要,因为这些函数组或模块组就是您必须能够重用的。

让我们继续假想的页面构建示例。这里,该模块的职责是用 HTML 构建一个页面。(现在让我们先略过细节问题和代码,因为示例的目的只是为了说明:在提高可重用性要素的同时,如何使函数和函数组方便地相互配合。)

从内置的 PHP 函数开始,您可以构建抽象函数,使用它们创建更多处理基本需求的函数,然后依次使用这些函数构建特定于应用程序的函数。图 2 可以让您了解其工作原理。


现在,先在内存中构建页面,然后将完成的页面分发给浏览器。

在内存中构建页面有两大好处:

可以用自己的脚本高速缓存已完成的页面。
如果未能成功构建页面,可以废弃完成一半的页面,并使浏览器指向出错页面。
现在,您的用户将不会看到页面中有错误消息的报告了。

根据大多数页面的结构,需要将页面构建模块分成执行以下功能的函数:

绘制顶栏
绘制导航栏
显示内容
添加脚注
还需要执行下述功能的函数:

高速缓存页面
检查页面是否已经被高速缓存
如果页面已被高速缓存则显示它
让我们称之为页面构建器(pagebuilder)模块。

页面构建器模块通过查询数据库执行其工作。由于该数据库是 PHP 之外的,所以将使用数据库抽象模块,其职责是为 PHP 中各种不同的特定于供应商的数据库函数提供同类接口。该模块中的重要函数有:连接数据库的函数、查询数据库的函数以及提供查询结果的函数。

假定您还希望实现一个站点范围的搜索引擎。该模块将负责搜索站点上与某个关键字或某组关键字相关的文档,并根据搜索字符串的相关性或出现该字符串次数最多来显示结果。如果您还希望记录搜索以便进行审计,该模块将与数据库抽象模块一起使用。

请记住,您将接受来自用户的输入。您需要将其显示在屏幕上,并废弃那些看上去怀有恶意的内容。这需要另一个模块,它负责验证用户通过表单提交的数据。

至此,您对我正在讲述的概念肯定有了大致的了解。必须将最核心的功能分解成逻辑模块,要执行它们的任务,应用程序必须使用这些模块提供的函数。

使用这种分层的方法,简单的页面构建呈现应用程序可能如图 3 所示。


请注意,在本示例中,核心模块与处理应用程序的模块之间没有层次。也就是说,核心模块可以从下面的抽象模块或层中声明的函数调用和声明函数,但是应用程序代码可能不能这样做。如果应用程序代码中的函数受任何低层函数“污染”或者封装了任何低层函数,那么应用程序代码不应该声明这些函数。它只能使用低层的函数。这被证实是一个更快的方法。
既然您已经了解了应如何使用和编写函数,那么就让我们研究一些常用的技术。

简单点说,引用就象 C 语言中的指针。唯一的区别在于,在 PHP 中,不需要象在 C 语言中那样使用 * 运算符来解除引用。您可以将它们看成是变量、数组或对象的别名。无论执行什么操作,别名都将影响实际的变量。

清单 1 演示了一个示例。

<?php

$name = 'Amol';
$nom = &$name; // $nom is now a reference to $name
$nom .= ' Hatwar';

print("Are you $name?n"); // Jimmy Ray parody?

?>

当将参数传递给函数时,函数接收到参数的副本。只要函数一返回,您对参数所做的任何更改都将丢失。如果您希望直接改变参数,这会是一个问题。清单 2 演示了一个说明该问题的示例。


<?php

function half($num)
{
$num = $num / 2;
return $num;
}

$myNum = 15;
$result = half($myNum);

print("The half of $myNum is: $resultn");
print("$myNum contains: $myNumn");

?>

我们希望直接改变 $myNum;通过将 $myNum 的引用传递给 half() 函数可以轻易地完成该工作。但是请记住,这并不是个好实践。使用您代码的开发人员必须跟踪所用的引用。这可能会在不经意间导致错误蔓延。它还会影响到的函数的易用性。

更好的实践是直接在函数声明中使用引用 — 在我们的例子中,使用 half(&$num) 代替 half($num)。这样,通过记住引用,您就无须记住要将参数传递给函数了。

PHP 处理幕后的一些事情。较新的 PHP 版本(从 4.0 起的后续版本)不赞成在调用时按引用传递,并且无论如何都会发出警告。(这里有一些建议:如果您正在使用针对早期 PHP 版本编写的代码,那么最好更新代码,而不是通过改变 php.ini 文件来更改 PHP 的行为。)
常常需要维护函数调用之间的变量值。可以使用全局变量,但是变量非常脆弱,并可能被其它函数破坏。我们希望变量对于函数而言是局部变量,并仍然保留其值。

使用 static 关键字是一个很好的解决方案。当我希望计算在无法使用调试器的情况下有多少用户定义的函数被执行时,我常使用这种方法。我只是改变了所有函数(当然是使用自动化的脚本),并在函数体的第一行添加了对执行计数工作的函数的调用。清单 3 描述了该函数。


function funcCount()
{
static $count = 0;
return $count++;
}

刚好在脚本完成之前通过调用 funcCount() 来收集变量中的返回值,这种方法是有效的。令人吃惊的是,$count 没有复位为零;初始化静态变量的行只执行了一次。

如果您必须访问函数中的全局变量,那么在使用变量前您需要使用 global 关键字。

从 PHP 4 开始还可以这样做 — 先使用函数,然后再定义它,只要您不会试图声明该函数两次即可。
在许多情形中,您会发现实际上您并不知道接着必须调用哪个函数。当您在进行事件驱动的编程时,或者当您希望在触发了系统外的某一事件时调用特定函数时,就会出现这类情形。通过网络进行通信的脚本就是这种情形的例证。

该方法类似于使用变量名。只要使用外部事件来设置变量,并且使用它作为函数(假定您已经声明了对应的函数)。有些迷惑吗?清单 4 做了澄清。


<?php

function say_hi()
{
print("Hi! ");
}

function say_greeting()
{
print("How are you today?n");
}

function say_bye()
{
print("Enough functions for the day, I hope to see you again next month.n");
print("Till then, have a good timen");
}

// Lets pretend someone just logged in
$my_func = 'say_hi';
$my_func();

// Greet the user
$my_func = 'say_greeting';
$my_func();

// Call it a day
$my_func = 'say_bye';
$my_func();

?>

当您想省事时,也可以使用该方法编写几条 switch-case 语句,以评估要使用哪个函数。只需设置变量并使用它作为函数。尽管这里我们故意设置了变量,但是请记住,可以动态完成该工作,而这才显示了该技术的功能是多么强大。
在本文中,我们阐述了如何设计和编写优质函数。我们演示了如何使模块和脚本集相互配合,以制作更大的应用程序,我们还研究了可以减少编码工作并生成极佳代码的技术。

在下一篇文章中,我们将说明 PHP 中的类和对象,以当前的技能为基础来进行构建,并且仔细研究一些执行高速缓存和数据库抽象的代码。



在 Developer Shed 的“The Art Of Software Development: Understanding Need”一文中,icarus 着重介绍了应用程序开发周期的第一部分,阐述了在您坐下来编写第一行代码前必须做的一些事情。
请阅读 Developer Shed 上 Harish Kamath 的教程“Using PHP with Java”;该教程包含详尽的说明性代码示例,它们已经在带有 JDK 1.3.0、Apache 1.3.20 和 PHP 4.1.1 的 Linux/i586 上测试过。
Mike Britton 的文章“Scratching the Surface: Getting Started with PHP Fusebox”,完整地介绍了最新的 Fusebox 版本 — 它是一个可伸缩的且有效的 Web 盒样式的体系架构工具。
PHPGuy 提供了一篇教程“Useful PHP Functions To Build Your PHP Toolkit”,提供了 10 种不寻常的函数,在利用 PHP 编码的时候,您可以使用这些函数节省时间。
还是出自 PHPGuy 的文章:“Making Sense of Those Cold PHP Errors!”说明了几种 PHP 错误类型,以及如何在开发期间弄清它们的含义。
PHP Debugger 可(免费)用于 PHP 代码的概要分析和调试。
本系列的第 1 部分“高屋建瓴”(developerWorks,2002 年 8 月)讨论了如何为掌握 PHP 打下坚实的基础。
本系列的第 2 部分“有效地使用变量”(developerWorks,2002 年 9 月)展示了如何通过构造配置文件解析器(使用不同的变量名)使脚本配置变得简单。
请访问 dW Linux 专区以获取更多参考资料。