明辉站/技术开发/内容

用 PHP 开发健壮的代码(3):编写可重用函数

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

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

深入研究
函数可用于:

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

简化代码。

最重要的是,将应用程序作为更小的应用程序相互协调的产物。
对于从编译语言(如 C/C++)转到 PHP 的开发人员来说,PHP 的性能级别是令人吃惊的。在使用 CPU 和内存资源方面,用户定义的函数非常昂贵。这主要是因为 PHP 是解释型和松散类型的。

包装与否
有些开发人员仅仅因为不喜欢函数的名称就把他们使用的每个函数都包装起来,而另一些开发人员却根本不喜欢使用包装。

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

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

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

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

图 1. 可管理性/速度 Vs. 函数数量


命名函数
正如我在本系列文章的第 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 可以让您了解其工作原理。

图 2. 分层的函数


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

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

可以用自己的脚本高速缓存已完成的页面。


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

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

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

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

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

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

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

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

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

图 3. 分层的页面构建应用程序


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

功能型技术
既然您已经了解了应如何使用和编写函数,那么就让我们研究一些常用的技术。

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

清单 1 演示了一个示例。

清单 1. 变量引用
<?php

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

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

?>



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

清单 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 描述了该函数。

清单 3. 计数用户定义的函数
function funcCount()
{
static $count = 0;
return $count++;
}




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

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

从 PHP 4 开始还可以这样做 — 先使用函数,然后再定义它,只要您不会试图声明该函数两次即可。

执行动态调用
在许多情形中,您会发现实际上您并不知道接着必须调用哪个函数。当您在进行事件驱动的编程时,或者当您希望在触发了系统外的某一事件时调用特定函数时,就会出现这类情形。通过网络进行通信的脚本就是这种情形的例证。

该方法类似于使用变量名。只要使用外部事件来设置变量,并且使用它作为函数(假定您已经声明了对应的函数)。有些迷惑吗?清单 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 专区以获取更多参考资料。

关于作者
Amol Hatwar 从能记事起就开始接触计算机。作为 GNU/Linux 的绝对拥护者,他为过去在 Microsoft 平台上编程感到内疚。他现在作为独立顾问帮助众多公司迁移到 GNU/Linux。作为开发 Web 应用程序领域的专家,他把所剩无几的空余时间花在研究没人听说过的技术上。他现在的兴趣包括开放源码软件、Web 服务、对等计算以及高可用性群集。

……

相关阅读