用 PHP 開發(fā)健壯的代碼(3):編寫可重用函數(shù)
發(fā)表時間:2023-08-16 來源:明輝站整理相關軟件相關文章人氣:
[摘要]在本系列文章(有關如何在實際情況下開發(fā)有效的 PHP 代碼)的第 3 部分中,Amol Hatwar 討論了如何構(gòu)建最有效的功能型函數(shù),使用這些函數(shù)不會犧牲太多性能或可管理性。作者重點闡述了如何編寫...
在本系列文章(有關如何在實際情況下開發(fā)有效的 PHP 代碼)的第 3 部分中,Amol Hatwar 討論了如何構(gòu)建最有效的功能型函數(shù),使用這些函數(shù)不會犧牲太多性能或可管理性。作者重點闡述了如何編寫可重用函數(shù),并介紹了如何避免與該任務相關的一些最常見問題。
歡迎回來。在本系列文章的第 1 部分中,我討論了一些基本的 PHP 設計規(guī)則,并介紹了如何編寫安全、簡單、與平臺無關且快速的代碼。在第 2 部分中,我介紹了變量,并討論了它們在 PHP 編碼中的用法 — 好的和壞的實踐。
在本文中,您將了解如何在 PHP 中明智地使用函數(shù)。在每一種高級編程語言中,程序員都可以定義函數(shù),PHP 也不例外。唯一的區(qū)別在于,您不必擔心函數(shù)的返回類型。
深入研究
函數(shù)可用于:
將幾行代碼封裝成一條語句。
簡化代碼。
最重要的是,將應用程序作為更小的應用程序相互協(xié)調(diào)的產(chǎn)物。
對于從編譯語言(如 C/C++)轉(zhuǎn)到 PHP 的開發(fā)人員來說,PHP 的性能級別是令人吃驚的。在使用 CPU 和內(nèi)存資源方面,用戶定義的函數(shù)非常昂貴。這主要是因為 PHP 是解釋型和松散類型的。
包裝與否
有些開發(fā)人員僅僅因為不喜歡函數(shù)的名稱就把他們使用的每個函數(shù)都包裝起來,而另一些開發(fā)人員卻根本不喜歡使用包裝。
包裝現(xiàn)有的 PHP 函數(shù)而不添加或補充現(xiàn)有的功能,是完全不能接受的。除了會增加大小和執(zhí)行時間外,這樣的重命名函數(shù)有時可能會帶來管理上的惡夢。
代碼中的內(nèi)聯(lián)函數(shù)會導致莫名其妙的代碼,甚至是更大的管理災難。這樣做的唯一好處可能就是得到一個更快的代碼。
更明智的方法是,僅在需要多次使用代碼,并且對于您希望實現(xiàn)的任務沒有可用的內(nèi)置 PHP 函數(shù)時才定義函數(shù)。您可以選擇重命名或僅當需要時才有限制地使用。
圖 1 中的圖表粗略地顯示了可管理性和速度與使用的函數(shù)數(shù)量之間的相互關系。(在此我沒標明單位,因為數(shù)字取決于個體和團隊的能力;這一關系是重要的可視數(shù)據(jù)。)
圖 1. 可管理性/速度 Vs. 函數(shù)數(shù)量
命名函數(shù)
正如我在本系列文章的第 2 部分(請參閱參考資料)中提到的,要獲得有效的代碼管理,始終都使用公共的命名約定是必不可少的。
其它兩個需要考慮的實踐是:
選擇的名稱應當能很好地提示函數(shù)的功能。
使用表明包或模塊的前綴。
假定您有一個名為 user 的模塊,它包含用戶管理函數(shù),那么對于檢查用戶當前是否在線的函數(shù)而言,諸如 usr_is_online() 和 usrIsOnline() 這樣的函數(shù)名稱都是上佳之選。
將上面的名稱與 is_online_checker() 這樣的函數(shù)名稱相比較。得到的結(jié)論是,使用動詞優(yōu)于使用名詞,因為函數(shù)始終都會做點什么。
多少參數(shù)?
很有可能您將使用已經(jīng)構(gòu)造的函數(shù)。即使情形并非如此,您可能也希望最大化代碼的使用范圍。要做到這一點,您和其他開發(fā)人員應當繼續(xù)開發(fā)易于使用的函數(shù)。沒人喜歡使用那些所帶的參數(shù)既晦澀又難于理解的函數(shù),因此請編寫易于使用的函數(shù)。
選擇一個能夠說明函數(shù)用途的名稱(并減少函數(shù)使用的參數(shù)數(shù)量)是確保易于使用的一個好方法。參數(shù)數(shù)量的幻數(shù)是什么呢?依我看來,超過三個參數(shù)就會使函數(shù)難以記憶。使用大量參數(shù)的復雜函數(shù)幾乎都能被拆分成多個更簡單的函數(shù)。
沒人喜歡使用湊合的函數(shù)。
編寫優(yōu)質(zhì)函數(shù)
假定您希望在將 HTML 文檔放到瀏覽器之前設置文檔的標題。標題就是 <head>...</head> 標記之間的所有內(nèi)容。
假定您希望設置 title 和 meta 標記。不使用 setHeader(title, name, value) 函數(shù)執(zhí)行所有工作,而分別使用 setTitle(title) 和 setMeta(name, value) 完成各項工作是一個更佳的解決方案。該方案相互獨立地設置 title 和 meta標記。
進一步考慮,標題可以只包含一個 title 標記,但它可以包含多個 meta 標記。如果需要設置多個 meta 標記,則代碼必須多次調(diào)用 setMeta()。在這種情況下,更佳的解決方案是將帶有名稱-值對的兩維數(shù)組傳遞給 setMeta(),并且讓函數(shù)循環(huán)執(zhí)行該數(shù)組 — 同時執(zhí)行所有操作。
一般來說,象這樣的同時的函數(shù)更可取。用函數(shù)需要處理的所有數(shù)據(jù)一次性調(diào)用函數(shù),始終優(yōu)于多次調(diào)用函數(shù),并以增量的方式為其提供數(shù)據(jù)。編寫函數(shù)時的主要思想是,盡量減少從其它代碼對其的調(diào)用。
據(jù)此,setHeader() 解決方案實在不是好方法。顯而易見,我們可以將 setHeader() 重構(gòu)成 setHeader(title, array),但是也必須考慮到我們失去了相互獨立地設置 title 和 meta 標記的能力。
此外,在實際環(huán)境中,標題可能包含多個標記,而不只是 title 和 meta 標記。如果需要添加更多標記,您必須更改 setHeader(),并且改變依賴于它的所有其它代碼。在后一種情形下,只需多編寫一個函數(shù)。
下面的等式適用于所有編程語言:
便于記憶的名稱 + 清晰的參數(shù) + 速度和效率 = 在所有編程語言中都適用的優(yōu)質(zhì)函數(shù)
用分層的方法協(xié)調(diào)函數(shù)
函數(shù)很少是獨自存在的。它們與其它函數(shù)共同起作用,交換和處理數(shù)據(jù)以完成任務。編寫可與相同組或模塊中的其它函數(shù)良好協(xié)作的函數(shù)很重要,因為這些函數(shù)組或模塊組就是您必須能夠重用的。
讓我們繼續(xù)假想的頁面構(gòu)建示例。這里,該模塊的職責是用 HTML 構(gòu)建一個頁面。(現(xiàn)在讓我們先略過細節(jié)問題和代碼,因為示例的目的只是為了說明:在提高可重用性要素的同時,如何使函數(shù)和函數(shù)組方便地相互配合。)
從內(nèi)置的 PHP 函數(shù)開始,您可以構(gòu)建抽象函數(shù),使用它們創(chuàng)建更多處理基本需求的函數(shù),然后依次使用這些函數(shù)構(gòu)建特定于應用程序的函數(shù)。圖 2 可以讓您了解其工作原理。
圖 2. 分層的函數(shù)
現(xiàn)在,先在內(nèi)存中構(gòu)建頁面,然后將完成的頁面分發(fā)給瀏覽器。
在內(nèi)存中構(gòu)建頁面有兩大好處:
可以用自己的腳本高速緩存已完成的頁面。
如果未能成功構(gòu)建頁面,可以廢棄完成一半的頁面,并使瀏覽器指向出錯頁面。
現(xiàn)在,您的用戶將不會看到頁面中有錯誤消息的報告了。
根據(jù)大多數(shù)頁面的結(jié)構(gòu),需要將頁面構(gòu)建模塊分成執(zhí)行以下功能的函數(shù):
繪制頂欄
繪制導航欄
顯示內(nèi)容
添加腳注
還需要執(zhí)行下述功能的函數(shù):
高速緩存頁面
檢查頁面是否已經(jīng)被高速緩存
如果頁面已被高速緩存則顯示它
讓我們稱之為頁面構(gòu)建器(pagebuilder)模塊。
頁面構(gòu)建器模塊通過查詢數(shù)據(jù)庫執(zhí)行其工作。由于該數(shù)據(jù)庫是 PHP 之外的,所以將使用數(shù)據(jù)庫抽象模塊,其職責是為 PHP 中各種不同的特定于供應商的數(shù)據(jù)庫函數(shù)提供同類接口。該模塊中的重要函數(shù)有:連接數(shù)據(jù)庫的函數(shù)、查詢數(shù)據(jù)庫的函數(shù)以及提供查詢結(jié)果的函數(shù)。
假定您還希望實現(xiàn)一個站點范圍的搜索引擎。該模塊將負責搜索站點上與某個關鍵字或某組關鍵字相關的文檔,并根據(jù)搜索字符串的相關性或出現(xiàn)該字符串次數(shù)最多來顯示結(jié)果。如果您還希望記錄搜索以便進行審計,該模塊將與數(shù)據(jù)庫抽象模塊一起使用。
請記住,您將接受來自用戶的輸入。您需要將其顯示在屏幕上,并廢棄那些看上去懷有惡意的內(nèi)容。這需要另一個模塊,它負責驗證用戶通過表單提交的數(shù)據(jù)。
至此,您對我正在講述的概念肯定有了大致的了解。必須將最核心的功能分解成邏輯模塊,要執(zhí)行它們的任務,應用程序必須使用這些模塊提供的函數(shù)。
使用這種分層的方法,簡單的頁面構(gòu)建呈現(xiàn)應用程序可能如圖 3 所示。
圖 3. 分層的頁面構(gòu)建應用程序
請注意,在本示例中,核心模塊與處理應用程序的模塊之間沒有層次。也就是說,核心模塊可以從下面的抽象模塊或?qū)又新暶鞯暮瘮?shù)調(diào)用和聲明函數(shù),但是應用程序代碼可能不能這樣做。如果應用程序代碼中的函數(shù)受任何低層函數(shù)“污染”或者封裝了任何低層函數(shù),那么應用程序代碼不應該聲明這些函數(shù)。它只能使用低層的函數(shù)。這被證實是一個更快的方法。
功能型技術(shù)
既然您已經(jīng)了解了應如何使用和編寫函數(shù),那么就讓我們研究一些常用的技術(shù)。
使用引用
簡單點說,引用就象 C 語言中的指針。唯一的區(qū)別在于,在 PHP 中,不需要象在 C 語言中那樣使用 * 運算符來解除引用。您可以將它們看成是變量、數(shù)組或?qū)ο蟮膭e名。無論執(zhí)行什么操作,別名都將影響實際的變量。
清單 1 演示了一個示例。
清單 1. 變量引用
<?php
$name = 'Amol';
$nom = &$name; // $nom is now a reference to $name
$nom .= ' Hatwar';
print("Are you $name?n"); // Jimmy Ray parody?
?>
當將參數(shù)傳遞給函數(shù)時,函數(shù)接收到參數(shù)的副本。只要函數(shù)一返回,您對參數(shù)所做的任何更改都將丟失。如果您希望直接改變參數(shù),這會是一個問題。清單 2 演示了一個說明該問題的示例。
清單 2. 將參數(shù)傳遞給函數(shù)時的問題
<?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() 函數(shù)可以輕易地完成該工作。但是請記住,這并不是個好實踐。使用您代碼的開發(fā)人員必須跟蹤所用的引用。這可能會在不經(jīng)意間導致錯誤蔓延。它還會影響到的函數(shù)的易用性。
更好的實踐是直接在函數(shù)聲明中使用引用 — 在我們的例子中,使用 half(&$num) 代替 half($num)。這樣,通過記住引用,您就無須記住要將參數(shù)傳遞給函數(shù)了。
PHP 處理幕后的一些事情。較新的 PHP 版本(從 4.0 起的后續(xù)版本)不贊成在調(diào)用時按引用傳遞,并且無論如何都會發(fā)出警告。(這里有一些建議:如果您正在使用針對早期 PHP 版本編寫的代碼,那么最好更新代碼,而不是通過改變 php.ini 文件來更改 PHP 的行為。)
保留函數(shù)調(diào)用之間的變量
常常需要維護函數(shù)調(diào)用之間的變量值?梢允褂萌肿兞浚亲兞糠浅4嗳,并可能被其它函數(shù)破壞。我們希望變量對于函數(shù)而言是局部變量,并仍然保留其值。
使用 static 關鍵字是一個很好的解決方案。當我希望計算在無法使用調(diào)試器的情況下有多少用戶定義的函數(shù)被執(zhí)行時,我常使用這種方法。我只是改變了所有函數(shù)(當然是使用自動化的腳本),并在函數(shù)體的第一行添加了對執(zhí)行計數(shù)工作的函數(shù)的調(diào)用。清單 3 描述了該函數(shù)。
清單 3. 計數(shù)用戶定義的函數(shù)
function funcCount()
{
static $count = 0;
return $count++;
}
剛好在腳本完成之前通過調(diào)用 funcCount() 來收集變量中的返回值,這種方法是有效的。令人吃驚的是,$count 沒有復位為零;初始化靜態(tài)變量的行只執(zhí)行了一次。
如果您必須訪問函數(shù)中的全局變量,那么在使用變量前您需要使用 global 關鍵字。
從 PHP 4 開始還可以這樣做 — 先使用函數(shù),然后再定義它,只要您不會試圖聲明該函數(shù)兩次即可。
執(zhí)行動態(tài)調(diào)用
在許多情形中,您會發(fā)現(xiàn)實際上您并不知道接著必須調(diào)用哪個函數(shù)。當您在進行事件驅(qū)動的編程時,或者當您希望在觸發(fā)了系統(tǒng)外的某一事件時調(diào)用特定函數(shù)時,就會出現(xiàn)這類情形。通過網(wǎng)絡進行通信的腳本就是這種情形的例證。
該方法類似于使用變量名。只要使用外部事件來設置變量,并且使用它作為函數(shù)(假定您已經(jīng)聲明了對應的函數(shù))。有些迷惑嗎?清單 4 做了澄清。
清單 4. 動態(tài)函數(shù)調(diào)用
<?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 語句,以評估要使用哪個函數(shù)。只需設置變量并使用它作為函數(shù)。盡管這里我們故意設置了變量,但是請記住,可以動態(tài)完成該工作,而這才顯示了該技術(shù)的功能是多么強大。
結(jié)束語
在本文中,我們闡述了如何設計和編寫優(yōu)質(zhì)函數(shù)。我們演示了如何使模塊和腳本集相互配合,以制作更大的應用程序,我們還研究了可以減少編碼工作并生成極佳代碼的技術(shù)。
在下一篇文章中,我們將說明 PHP 中的類和對象,以當前的技能為基礎來進行構(gòu)建,并且仔細研究一些執(zhí)行高速緩存和數(shù)據(jù)庫抽象的代碼。
參考資料
在 Developer Shed 的“The Art Of Software Development: Understanding Need”一文中,icarus 著重介紹了應用程序開發(fā)周期的第一部分,闡述了在您坐下來編寫第一行代碼前必須做的一些事情。
請閱讀 Developer Shed 上 Harish Kamath 的教程“Using PHP with Java”;該教程包含詳盡的說明性代碼示例,它們已經(jīng)在帶有 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 盒樣式的體系架構(gòu)工具。
PHPGuy 提供了一篇教程“Useful PHP Functions To Build Your PHP Toolkit”,提供了 10 種不尋常的函數(shù),在利用 PHP 編碼的時候,您可以使用這些函數(shù)節(jié)省時間。
還是出自 PHPGuy 的文章:“Making Sense of Those Cold PHP Errors!”說明了幾種 PHP 錯誤類型,以及如何在開發(fā)期間弄清它們的含義。
PHP Debugger 可(免費)用于 PHP 代碼的概要分析和調(diào)試。
本系列的第 1 部分“高屋建瓴”(developerWorks,2002 年 8 月)討論了如何為掌握 PHP 打下堅實的基礎。
本系列的第 2 部分“有效地使用變量”(developerWorks,2002 年 9 月)展示了如何通過構(gòu)造配置文件解析器(使用不同的變量名)使腳本配置變得簡單。
請訪問 dW Linux 專區(qū)以獲取更多參考資料。
關于作者
Amol Hatwar 從能記事起就開始接觸計算機。作為 GNU/Linux 的絕對擁護者,他為過去在 Microsoft 平臺上編程感到內(nèi)疚。他現(xiàn)在作為獨立顧問幫助眾多公司遷移到 GNU/Linux。作為開發(fā) Web 應用程序領域的專家,他把所剩無幾的空余時間花在研究沒人聽說過的技術(shù)上。他現(xiàn)在的興趣包括開放源碼軟件、Web 服務、對等計算以及高可用性群集。