撰寫習慣、細節觀念
每種程式語言都有屬於它自己的特性,良好的撰寫習慣可能程式具有可閱讀性,幫助理解方便維護。能夠充分熟悉它的人在撰寫時越容易避免預期外的錯誤,以減少不必要的除錯動作,增加工作效率,因此此篇文章主要用來整理在學習 PHP 過程中應培養的撰寫習慣及應注意的細節觀念…等等的心得。
基本的撰寫習慣
- 檔案開頭以
<?php
做為 PHP 的起始標籤,盡量避免使用縮寫<?
。 - 檔案若為單純的 PHP 程式碼,則應省略 PHP 的結尾標籤
?>
,僅於結尾處加上註解表示檔案結尾。?>
結尾標籤在 PHP 編譯器中是非必要的。- 可避免他人在結尾標籤之後加上不可見的字元(空白、換行、TAB等等),可能會破壞頁面輸出的字元。
- 本網誌的範例程式碼會使用
?>
做為結尾符號,以保持程式碼一致性。<?php echo 'Hello World';
- 習慣為程式碼加上必要的註解。
- 養成加上註解的習慣,不但能幫助自己記憶,也能讓別人更容易看懂程式碼的用途。
- 手動對變數做初始化的動作。
- 在 PHP 中使用變數時,若該變數尚未被定義,PHP 編譯器會丟出 E_NOTICE 訊息,並自動替變數做初始化的動作。
- 雖然 E_NOTICE 訊息並不會影響程式執行,卻容易使程式碼變得不嚴謹且產生模糊地帶。
- 建議習慣手動對變數做初始化的動作,以增加程式嚴謹性。
<?php $array = array(); if (5 > 2) { $array[] = 5; } else { $array[] = 2 } print_r($array); ?>
- 純字串使用單引號 (Single Quote) 為主。
- 若字串僅為純文字,不需要經過轉換特殊字元的動作,建議使用單引號即可,避免 PHP 編譯器進行多餘的動作可增加效能。
- 使用單引號僅會對字串進行少數幾個特殊字元轉換的動作,如
\'
\\
。 - 雙引號包覆的字串內容則會經過完整的轉換,例如變數內容置換、特殊字元轉換等操作,如\n(換行)、\(製表符)...。
- 記住一個html的觀念,就是換行或是空白,是要透過<br /> <p> 等等去輸出的你在原始碼內換行是沒有作用的,瀏覽器不會自動幫你顯示出來(原始碼的換行只是好看好整理用的)
- 提供你兩個作法
- <pre>的作用是原始碼排版即顯示結果
- nl2br($output); 作用是幫你把換行字元轉成<br>
<?php $name = 'Eden'; echo 'Hello $name'; // $name 會被當作純文字,不做任何變數替換的動作 echo 'Hello $name\n'; // \n 也是被當作純文字,不會被轉換成換行字元 echo "Hello $name"; // $name 會被替換為 Eden echo "Hello $name\n"; // \n 會被轉換成換行字元 echo "Hello $name", PHP_EOL; // 換行字元建議改用 PHP_EOL 來輸出 ?>
- 使用陣列時,其陣列索引應盡量以單引號或雙引號括起來。
- 若以嚴謹標準來看
$array[ID]
的寫法是錯誤的,其陣列索引應該要被引號括起來為$array['ID']
較為正確。 - PHP 編譯器會將沒有括起來的字串索引當作「祼字符」,先將它解釋成
Constant
後找不到其定義時,才再將它重新解釋成字串,因此$array[ID]
是在這項特性之下被正常執行的。- 這項特性所提供的彈性是用執行效能換來的 :
$array['ID']
的執行速度是$array[ID]
的七倍。 - 即使陣列索引本身是數值,其
$array[0]
與 `$array['0'] 都是指向同一個陣列元素。
- 這項特性所提供的彈性是用執行效能換來的 :
- 因此建議在非必要的情況下,使用陣列時務必將索引括起來。
<?php $prices = array('Milk' => 30, 'Tea' => 15, 'Coffee' = 35); echo $prices['Milk']; # 索引以單引號括住 // 使用 Constant 存取陣列 define('EDEN', 'Tea'); # 將 EDEN 定義為祼字符 echo $prices[EDEN]; // 使用 Variable 存取陣列 $coffee = 'Coffee'; echo $prices[$coffee]; ?>
- 若以嚴謹標準來看
- 使用外來變數時,先利用
isset()
或array_key_exists()
等相關機制來檢查變數是否存在。- 直接使用未被定義的變數時,PHP 會丟出 E_NOTICE 訊息。
- E_NOTICE 訊息不影響程式執行,且可透過設定 ERROR_REPORTING | DISPLAY_ERROR 的機制來忽略這類訊息。
- 但為避免非預期性的錯誤,建議在使用 $GET, $POST, $REQUEST 等外來變數,或無法確保變數是否已存在的情況時,記得先檢查該變數是否存在。
<?php /* 不夠嚴謹的寫法 */ // 1. 當 $_POST 不存在或送出的表單沒有 password/account 欄位時,PHP 會丟出 ENOTICE (undefined index) if ($_POST['password'] == 'PASSWORD') { $admin = $POST['account']; } // 2. 當上述 if 不成立時,$admin 就不會被定義,因此 PHP 會丟出 E_NOTICE (undefined variable) echo $admin . PHP_EOL; /* 較嚴謹且正確的寫法:變數先給預設值,且使用外來變數前先檢查是否存在 */ $admin = null; if (isset($POST['password']) && ($POST['password'] == 'PASSWORD')) { $admin = (isset($_POST['account'])) ? $_POST['account'] : $admin; } echo $admin . PHPEOL; // 除了利用 isset() 以外,也能透過 array_key_exists() 來檢查。 if (array_key_exists('password', $_POST)) { // Do something here. } ?>
- 直接使用未被定義的變數時,PHP 會丟出 E_NOTICE 訊息。
- 在使用
==
,!=
,===
,!==
比較運算子時,必須多加注意以避免預期外的錯誤。- 例如比較
Boolean
布林值時應盡量使用===
,!==
比較運算子以增加程式嚴謹度。 - 基於 PHP 自動轉換變數型態的特性,因此需注意比較運算子的差異。
==
,!=
比較的只有變數內容,當型態不同時會自動轉換成相同型態。===
,!==
除變數內容以外,還會比較變數的型態。- 更多詳細內容請參考官方手冊: PHP type comparison tables
- 例如比較
增加程式可讀性的撰寫習慣
- 找一個多數人認同的程式碼撰寫規範來遵守。
- 遵守一個多數人認同的程式碼規範,不僅可提高程式碼的可讀性,亦能讓團隊成員共同撰寫出具有相同規則的程式碼。
- PSR Coding Style for PHP (待補充)
優化效能的撰寫習慣
- 使用迴圈時,非必要應避免在迴圈條件式中直接使用函數。
- 若函式被寫在迴圈條件式中,在每次迴圈檢查條件式時都會執行該函式一次。
- 若函式回傳值在迴圈的過程中不會改變,則應避免將函式寫在迴圈條件式中。 (反覆執行函式卻取得同樣的結果,只是浪費系統資源及降低程式效能。)
<?php $array = range(0, 100000); # 產生陣列 // 不適當的寫法,因為 count($array) 反覆執行 N 次卻都是取得同樣的結果 for ($i = 0; $i < count($array); $i++) { echo $array($i); } // 較適當的寫法,count($array) 僅會執行一次 $arrayCount = count($array); for ($i = 0; $i < $arrayCount; $i++) { echo $array($i); } // [例外狀況] 將陣列元素增加至 11000 個 : count($array) 的回傳值會因迴圈過程中的動作而被改變 for ($i = 0; count($array) < 11000; $i++) { $array[] = $i; } ?>
- 盡量避免使用正規表達式 (Regular Expression) 。
- 在進行字串操作時,非必要應盡量避免使用正規表達式。
- 例如
str_replace()
的執行效率會比preg_replace()
快,
- 例如
- 在必要時仍以使用正規表達式達到簡化工作,容易維護為主。
- 例如檢查 Email 格式若以其他方式來完成,除了得費一番功夫以外,也不易維護。
- 在進行字串操作時,非必要應盡量避免使用正規表達式。
- 使用完變數之後,必要時手動釋放變數資源。
- 當變數被用以儲存龐大資料時,建應於使用完畢後立即使用
unset
以釋放該變數所佔有的資源。<?php $article = '假設這是一篇文字超長,佔記憶體至少 100MB 的文章...'; // Do something...以下數百行(略) unset($article); // 釋放變數資源 // Do something else 以下數百行(略) ?>
- 當變數被用以儲存龐大資料時,建應於使用完畢後立即使用
其他程式相關的細節
- 陣列宣告時,最後一個元素之後可以有多餘的逗點。
- 雖然有彈性但不建議用,因為在程式撰寫時還是保有一定的嚴謹度,以減少不可預期的錯誤。
<?php // 多數程式語言的寫法,最後一個元素之後有不能多餘的逗點 $array = array(1,2,3); // 由於 PHP 本身的彈性,最後一個元素之後可接受多餘的逗點 $array = array(1,2,3,); // 不會出現錯誤。 ?>
- 雖然有彈性但不建議用,因為在程式撰寫時還是保有一定的嚴謹度,以減少不可預期的錯誤。
- 陣列
$array[0]
與$array['0']
是指向同一個陣列元素。- 由於 PHP 具有自動轉換變數型態的特性,透過變數操作陣列時就產生了一個疑問:
- 若
$index = '5'
時,$array[$index]
究竟是 $array['5'] 還是 $array[5]?
- 若
- 因此就撰寫了簡單的測試碼,得出以下結果:
<?php $array = array( 0 => 'Number : 0', '0' => 'String : Zero' ); print_r($array); // 輸出結果只有 [0] => 'String : Zero' ?>
- 由於 PHP 具有自動轉換變數型態的特性,透過變數操作陣列時就產生了一個疑問:
- 使用函式傳遞參數時,若傳遞的參數是物件時為 Call-Time Pass By Reference 。
- 陣列及一般變數為 Pass By Value 的方式,可在函式定義時加上
&
符號強制改以 Pass By Reference 傳入。- PHP 5.3 時對 Call-Time Pass By Reference 做了些改變,當有 Pass By Reference 的需求時,必須在定義函式時明確指定該引數的傳入方式,且不可在呼叫函式時使用
&
符號。
- PHP 5.3 時對 Call-Time Pass By Reference 做了些改變,當有 Pass By Reference 的需求時,必須在定義函式時明確指定該引數的傳入方式,且不可在呼叫函式時使用
- 請參考以下範例:
<?php // 定義一個簡單的類別 Class MyObject { public $name = null; public function setName($name) { $this->name = $name; } } // 定義測試用的一般函式 function demoBasics($object, $array, $var) { $object->setName('Basics'); $array = array('Basics'); $var = 'Basics'; } // 定義測試用的函式 (引數前加上 & 表示強制以 Pass By Reference 方式傳入) function demoReference(&$object, &$array, &$var) { $object->setName('Reference'); $array = array('Reference'); $var = 'Reference'; } // 測試範例 $myObject = new MyObject(); $myObject->setName('Default'); $myArray = array('Default'); $myVar = 'Default'; // 正常情況下為 Pass By Value 傳遞參數 demoBasics($myObject, $myArray, $myVar); print_r($myObject); // 輸出 name => 'Basics',以 Pass by Reference 傳遞物件參數 print_r($myArray); // 輸出 [0] => 'Default' print_r($myVar); // 輸出 'Default'; // 強制以 Pass By Reference 傳遞參數 demoReference($myObject, $myArray, $myVar); print_r($myObject); // 輸出 name => 'Reference' print_r($myArray); // 輸出 [0] => 'Reference' print_r($myVar); // 輸出 'Reference'; // 註:下列這行程式碼在 PHP 5.3 以上版本時無法通過編譯 // 必須在定義函式時就明確指定變數以 Pass By Reference 的方式傳入。 demoReference(&$myObject, &$myArray, &$myVar); ?>
留言