首頁>技術>

第 4 章 迴歸原生態

就當前而言,PHP仍然是網站建設的主流程式語言之一。一方面,是得益於它自身的簡單性,容易學習且快速上手;另一方面,得益於開源社群貢獻的各種優秀框架、類庫和專案。這些原始碼下載到伺服器後,簡單配置一下,甚至都不需要二次開發就能直接使用,非常方便。

但需要注意的是,別人提供、貢獻的開源專案是可以減少我們重複開發的成本,並不意味著我們對原生態的PHP就可以置之不理。正好相反,更深入地理解PHP原生態的用法,將能幫助我們從底層、從根本上更透徹地理解和掌握別人封裝的類、函式、模組和擴充套件。也就是說,除了會使用,還不足矣。作為專業的開發人士,我們還應明白為什麼會這樣,洞明背後微妙的差異,對中大型專案開發尤其關鍵。關於這部分,後面會慢慢說到。

4.1 空與非空

很多時候,對於明顯的初級PHP語法,我們一眼就能識別。假設稍微轉換一下,這時就需要花點心思才能識破其中的奧妙。最困難的莫過於,微妙的用法一旦與繁雜的業務程式碼、規則邏輯混在一起,散落在上千行程式碼內時,想要在短時間內發現問題所在則是個巨大的挑戰。

4.1.1 簡單的判空

大家使用最多的PHP函式之一,也許是empty()這個函數了。而且,大家都知道,什麼樣的情況下,一個值會判斷為空。摘自官方文件的說明,以下的東西被認為是空的:

"" (空字串)0 (作為整數的0)0.0 (作為浮點數的0)"0" (作為字串的0)NULLFALSEarray() (一個空陣列)$var; (一個聲明瞭,但是沒有值的變數)

非常容易看出,以下程式碼輸出的結果為true。

<?php$var = 0;var_dump(empty($var)); // 結果輸出為true
4.1.2 隱晦的判空

但是,結合其他函式一起使用時,並且不再是直接使用empty()函式來判斷時,情況就開始變得晦澀了。一起來看下以下這段有問題的程式碼。看你需要多少時間才能發現裡面的BUG?

<?php// 文章內容$text = '軟體開發是根據使用者要求建造出軟體系統或者系統中的軟體部分的過程。……';  // 使用者輸入的關鍵字$keyword = '';  $pos = strpos($text, $keyword);if ($pos) {    // 找到了,是我感興趣的文章} else {    // 未能找到關鍵字}

這裡的場景是,根據使用者輸入的關鍵字,匹配某篇文章是不是讀者感興趣的。如果文章包含關鍵字就視為是使用者感興趣的,否則就是不感興趣的。正常情況下這段程式碼是可以正常工作的,問題在於,如果碰巧使用者輸入的關鍵字剛好出現在文章開頭時,就會發生意想不到的事情。例如,使用者輸入關鍵字“軟體”。由於“軟體”這個詞出現在最前面,所以查詢到的位置$pos值為0。最後在判斷位置時,0被當作FALSE,即:$pos = !empty($pos) = !empty(0) = !TRUE = FALSE。實際是找到匹配值,卻被誤判斷為未找到。由此就出現了一個BUG,引發了一個故障。

PHP全等判斷

這點很好理解,從我們開始接觸PHP程式設計時,就已經知道這一點了。全等判斷是指不僅要求值相等,而且還要求型別也一樣。即在不進行隱式型別轉換的情況下,待比較的這兩個值是否仍然相等。普通的相等,PHP預設會進行隱式的轉換,使用兩個等號 == 表示。全等判斷則用三個等號 === 表示。

針對前面剛才的問題,可以使用全等來修正對是否找到這一邏輯的判斷。即:

if ($pos !== FALSE) {    // 找到了,是我感興趣的文章} else {    // 未能找到關鍵字}

這一點是大家都知道的,但下面這一點也許大家都知道,但容易忽略。

PHP函式的錯誤返回

PHP底層的程式碼,可以分為兩大類。一類是早期面向過程正規化的函式,例如:strpos()、json_decode()、curl_exec()、file_get_contents()等函式。另一類是後期面向物件正規化的類與物件,例如:PDO、Memcached、SoapClient等。對於前者,當失敗時,例如字串查詢不到、JSON解碼失敗、URL抓取超時或者檔案不存在時,所呼叫的函式會返回布林值FALSE表示失敗。而對於後者,即若使用的是封裝的類,並透過類例項化的物件來操作時,當發生失敗或者異常情況時,則會直接丟擲異常。例如資料庫連線失敗、SOAP呼叫失敗。

這可以說是PHP語言的慣例。當呼叫函式並失敗時,返回FALSE,這裡需要做好全等判斷,避免與正常情況下的數字0、"" (空字串)或"0" (作為字串的0)混淆。前面提到的關鍵字位置查詢就是其中一例,又如檔案內容的讀取,若檔案存在但讀取的內容為空字串,與檔案不存在讀取到的結果為FALSE,這兩者之間是有著微妙的區別的。在進行專案開發時,一定要注意嚴格區分,否則就會失之毫釐,謬以千里。

此外,如果是函式失敗了,可以透過提供的錯誤碼查詢到對應的錯誤資訊。例如,使用curl失敗時,可以使用curl_error()函式檢視錯誤資訊。例如官網提供的示例:

<?php// 建立 cURL 控制代碼,指向一個不存在的位置$ch = curl_init('http://404.php.net/');curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);// 筆者注,使用全等判斷if(curl_exec($ch) === false){    // 筆者注,獲取錯誤資訊    echo 'Curl error: ' . curl_error($ch);}
4.1.4 為什麼系統崩潰了?

再來看一個更為複雜的真實案例場景。在一箇中型社交遊戲系統中,有一個業務場景是需要獲取每個使用者的道具數量。相關程式碼片段如下:

但該功能上線不久後,發現整個遊戲系統就崩潰了,經排查發現是資料庫負載過高導致系統無法正常響應。原來,有很多玩家使用者是沒有任何道具的,即他們的道具數量為0。當沒有快取時,透過Memcached讀取出來的值是FALSE,經隱式轉換後也是0。此時,就無法區分是使用者真的沒有道具,還是因為沒有快取需要直接查詢資料庫。最終導致了在高併發的情況下,頻繁穿透到資料庫,進行聚合的查詢,拖垮了資料庫伺服器。

這時的情況,會比以往都要嚴峻。首先,這裡不僅有前面討論的空判斷,還引入了Memcached快取和資料庫查詢。其次,這是確切發生在線上環境的故障問題,每一秒都在影響遊戲玩家的使用者體驗,每一分鐘都對我們的產品造成了損失,需要在面臨重大壓力、在最短的時間內找出原因並修復上線。最後,上面的程式碼片段是經過簡化提煉的程式碼,實際情況上,程式碼可能遍佈在你的專案裡,更為要命的是,你的專案擁有10萬行以上的程式碼!

對於這種情況,需要提前意識到會發生怎樣的狀況。改進的方式很簡單,一種是沿用前面的全等判斷;另一種就是,不要在快取結果中只儲存基本型別的資料,而是儲存一個結構體,即儲存一個數組到快取中。如:

4.1.5 小結

作為三大程式控制結構之一,選擇控制結構是我們平時專案開發過程中接觸最多的。這裡的條件邏輯判斷,又會涉及對空和非空的判斷。如果做出準確無誤的判斷,就要求我們對全等判斷、各函式失敗時的返回值、隱式型別轉換、空值的判斷等都要有一個全面的認識和清晰的理解。這樣才不會誤解、誤用、誤判。

10
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 系統的資料一致性到底是在說什麼?我到今天才算真明白了