◎本文原載網站製作學習誌,原文章連結按此。
WebConf Taiwan 2013 是今年我所知道的第一個大型 Conference ,如果說只能用一個字表達這次 WebConf 的參加心得,那就是:讚!
在台灣已經有好幾個大型的 Conference ,但針對網站製作這個主軸來舉辦的可以說是非常罕見。而 WebConf 的議題不僅包含了 Web 開發、設計等專業領域,更有創業、團隊溝通等讓人值得省思的議題。許多朋友都表示,真的非常希望有分身術來聆聽每一場演講,就可以知道這些議題對於大家的吸引力有多高。
我也很榮幸地受邀演講,題目是「如何成為一個更好的 PHP 開發者」。
其實 PHP 這個主題並不是我一開始想說的,但龍哥認為我應該要幫國內 PHP 開發者打一劑強心針;因此我把原來想講的內容揉合了 PHP ,然後就是這次的內容了。以下是投影片連結。
可惜這次有些內容沒有時間講,所以我把這次的內容用文字的方式再次分享給大家。
我爸曾經是個水電師父,所以國中時代,我常跟著他跑工地幫忙。有時我們會用到混凝士來填補一些被我們施工過的水泥牆面,這種簡單的工作就落到我身上。其實當時年紀小,什麼也不懂,就有樣學樣地把水、沙和混凝土混在一起,然後就去補了。結果就是爛泥扶不上牆。
其實水、沙和混凝土的比例,會依照需求而有不同,像是地基用的水泥要摻入碎石子;要防水的話,得加入洋菜粉。除了水泥,我們有時也需要用到磚頭來做一些小工程。有經驗的師傅對磚頭的特性都非常瞭解,他們會用榔頭將磚塊敲出適合的大小,再將這些敲下來的磚頭填補到空隙中。我老是搞不定的水泥和磚頭,在這些師傅手上,乖得服服貼貼。
有個工匠師傅對我這麼說過:如果想蓋一道磚牆,而不瞭解水泥和磚頭的話,你只會蓋出一道越來越歪的牆。但多數客戶其實不在乎這些,他只要你蓋出堅固的房子。不過如果你沒有花心思在這些小細節上,即便你蓋出了外表漂亮的房子,但幾個月後就開始龜裂,不必地震來就自己倒了。
水泥、磚頭,這些都是我們認為沒什麼的東西,但它們都要專業的知識與技術。可是在台灣,很多老闆一直認為專業是可以被取代的,今天這工作你不做,我還是可以找到別人來完成。他們常常強調自己縱橫商場的知識與經驗,但就是絕口不提員工們的技能需要專業經驗的累積。所以「微薪、技佳」就成了台灣員工最好的寫照。
而在軟體界,這種思維更是隨處可見。軟體業界通常需要很多 PHP 開發者,但 PHP 卻總是被當做沒什麼技術性的語言來看待。歸究原因, PHP 面臨了以下問題:
第一點:業界普遍認為 PHP 不需要什麼高門檻就可以上手,很多人認為只要學會對資料庫進行 CRUD 的操作,就懂 PHP 了;因此相較於其他語言的開發者, PHP 開發者的地位就顯得更低了。
第二點在於觀念的陳舊:我相信絕大部份的人對 PHP 的觀念還停留在 PHP 4 的時代,甚至認為 PHP 一定會跟 HTML 混在一起。另外坊間很多入門書籍,也都是把以前的內容加上新的 PHP 5 語法後繼續推出賣錢。新的觀念很少被提到,也造成了大家認為 PHP 就是這樣寫的錯誤認知。
第三點則是社群:社群原本就應該是多元化的,但 PHP 社群的問題在於大家都有自己的一套想法。當這些想法沒有被統整起來,而是每個人都用自己的實作來發佈時,那麼就很容易讓入門的人無所適從。
那 PHP 是不是真的不好呢?其實 PHP 確實有它的包袱存在,但那並不表示它沒有持續在進步。在學習 PHP 時,你可以先問自己幾個問題:
第一個問題:剛開始 PHP 並不是一個語言,它的目的是讓一些對程式不甚瞭解的網頁開發者,可以快速為自己的網頁加入互動功能。這是 PHP 的原罪,畢竟它一開始的目的就是如此。但是時至今日, PHP 已經漸漸朝向一個完整的語言前進了,許多軟體工程的知識都可以在 PHP 上面應用。
第二個問題:如果你寫了一陣子 PHP 之後,發現它不論在語法或是觀念上還是不適合自己,那就要趁早遠離它,因為這樣可以增加你學習其他語言的時間。但如果你已經投入心力在寫 PHP 或是已經在靠 PHP 吃飯,那麼就把它學得更專業些吧。
那麼要怎麼讓自己的 PHP 技能更專業呢?以下九式,將協助大家在 PHP 這門功夫上更上一層樓。
身為一個 PHP 開發者,首先當然是要多瞭解 PHP 。而瞭解 PHP 則要從以下幾個方向著手:
多數 PHP 開發者不敢嘗試新版本,通常是因為他們害怕自己寫的程式碼在新的 PHP 版本會爆炸。就我個人的經驗來說,如果沒用太多奇技淫巧的話,其實版本的升級並不是太大的問題。大致上會有問題的部份在於 PHP 4 轉到 PHP 5 ,以及 PHP 5.2 轉到 PHP 5.3 這兩個部份。在官方手冊上就有版本轉移的指導供大家參考。
身為一個 PHP 開發者,對於 PHP 運作的環境一定要充份掌握。像是怎麼開啟 PHP 的錯誤通知, PHP Session 是存放在哪裡,或是 Web Server 是怎麼把瀏覽器的請求轉發給 PHP 執行,以及作業系統等等。越瞭解 PHP 的運作環境,將會對你找到程式的問題來源越有幫助。
雖然 PHP 一開始是在 Web 上執行,但它也常在命令列模式中擔任 Shell Script 的角色。這兩種模式在執行上都有它的不同的地方,這些差異就會影響我們在開發程式上的思考方式。
前面說過 PHP 原本並不是從語言的角度出發,所以它後來發展出了較為獨特的語言架構。
例如我們可以用 list 來交換兩個變數內容。
// 交換變數內容 list($a, $b) = array($b, $a);
如果是會回傳陣列的函式,也可以用 list 來直接取得指定位置的值。
// 直接取得指定位置的值 list( , $el) = getElements();
而在 PHP 5.4 之後,我們可以用方括號來直接取用元素。
// PHP 5.4 :用方括號來直接取用元素 $el = getElements()[1];
也許 PHP 不像其他語言一開始就將這些語法考慮進來,但若是能更深入瞭解 PHP 語法的話,那麼程式碼就可以更精鍊一些。
PHP 也已經提供很多有用的函式,但很多開發者常常忽略它們,然後就自己寫一段相同作用的程式碼了。
舉個例子:如果我們想知道第一個陣列裡的元素是否存在於第二個陣列裡,那麼我們可能會寫出以下的程式碼。
// Bad $diff_ids = array(); foreach ($ids as $id) { if (!in_array($id, $actual_ids)) { $diff_ids[] = $id; } }
但 PHP 其實有個 array_diff 函式可以使用,而且速度絕對比我們自己寫的程式碼快得多。
// Good $diff_ids = array_diff($ids, $actual_ids);
所以多去瞭解 PHP 提供了什麼函式讓我們使用,這樣寫出來的程式碼才能事半功倍。
雖然在 PHP 上已經有一堆的不錯的工具可以使用,但這不表示我們不需要瞭解它們背後的原理,像是 HTTP 請求與回應、資料庫存取、檔案存取等。當把這些原理弄清楚之後,就可以用更接近底層的思維來理解別人寫的程式碼。
在 PHP 5 推出的時候,加入了 Standard PHP Library 及一些內建的 Interface 。以往因為它們剛推出時變動很大,而且也不是有很多人瞭解它們,因此有關於它們的教學非常少。不過現在 SPL 已經相當成熟,熟悉它們的話可以幫我們寫出更簡潔的 PHP 程式碼。
除了瞭解 PHP 本身之外,我們也要有好的工具來讓我們的開發更順手。
首先是編輯器,最基本的要求一定要能支援 PHP 的語法高亮度。而好一點的編輯器也要能提供語法自動完成,並顯示相關的 API 說明。有些編輯器還能幫我們找到變數、函式或類別是在哪個檔案定義的,並且整合了其他開發工具。
這裡我推薦 NetBeans for PHP ,它在這幾個功能上面都表現得相當優秀,更重要的是它是免費軟體。其他還有 Eclipse PDT 或是 PHPStorm ,也都是非常好的選擇。
有時候在程式執行時總是會發生錯誤,我們會需要知道錯誤是如何發生的。而 PHP 預設的除錯訊息比較陽春,有時候不容易看出它的錯誤實際發生位置。這時候我們就要借重除錯工具的幫忙。好的除錯工具最基本的要有中斷點的設置,也可以顯示完整的執行堆棧資訊,當然變數內容追蹤也是必要的。這些特色都可以協助我們除錯。
通常有經驗的 PHP 開發者,都會在自己的開發環境上安裝 Xdebug 來讓 PHP 出現錯誤時,顯示更多有用的訊息。另外 FirePHP 也可以幫我們把錯誤訊息送到 Firefox 瀏覽器,方便我們在 Firebug 中直接查看 PHP 的相關資訊。
接著是版本控制,到現在,還有一些公司其實並沒有使用版本控制系統。這些公司的 PHP 開發人員可能還是習慣在線上備份舊程式碼,然後再把新程式碼利用 FTP 傳上去。這樣一來不僅會讓接手維護的人產生混亂,而也會有安全上的問題。
而版本控制系統的好處有以下幾點:
所以如果還沒有使用版本控制系統,那麼就直接選用目前最多人推薦的 Git 吧。各位可以先到 GitHub 上開一個 Repository 來練習 Git 的操作流程。至於教材的部份,我推薦 ihower 寫的 Git 教學。
如果把程式碼分五級,我想大致可以這麼分:
其他的開發者通常就是你身邊的伙伴,如果你寫的程式碼讓他們很頭大的話,就很難成為這個團隊的一份子。
在很多公司裡,即使部門裡有好幾位開發人員,但我想各自為政的狀況可能還是比較多一些。學習如何跟別人一起寫出好程式,我想是很多 PHP 開發者必須瞭解的課題。不過要真正成為一個團隊其實滿困難的,所以在初期,我們可以從幾個方向入手。
通常如果團隊裡選了一個 Framework ,那麼這個 Framework 的程式架構就必須要讓大家都熟悉。如此一來溝通與開發的成本就會相對減少很多,當然前提是不能抗拒團隊所選擇的 Framework ,除非這個 Framework 真的選得不好。
不少 PHP 開發者寫程式都很隨心所欲,可能這個月用這個風格,下個月又換了一套。其實這樣會阻礙團隊裡其他人來 Review 自己的程式碼。如果團隊已經有了一套編碼標準,那麼就遵守它吧。如果還沒有,那麼我推薦大家採用 PHP-FIG 這個社群所制定的 PSR 標準。
以往我所維護的 PHP 專案裡,都會用到很多高手寫的套件。不過這些專案都會有個共同的毛病:套件載入的方式參差不齊。每個接手維護的人都會有自己載入套件的習慣,導致整個專案的架構非常破碎。現在, PHP 社群推出了一個很棒的工具叫 Composer 。它可以幫我們把專案中的套件集中管理,甚至還可以讓我們做自動載入。如果你的專案裡會用到很多第三方套件,那麼相當推薦你試一試它。
光是學會跟其他人一起寫程式還是不夠的,如果我們所開發出來的程式碼不容易擴充與維護,那麼久了這個團隊也還是會分崩離析。
Any fool can write code that a computer can understand. Good programmers write code that humans can understand. - Martin Fowler
一個能夠被維護的程式碼,是被人看懂的程式碼。如果程式碼裡面沒有人話,那麼這段程式碼存在的意義就不見了。
我們可以利用抽象的角度來重新看待程式碼,把實作隔離在介面之後。在這方面的箇中好手,物件導向就是其中之一。例如我們常常會在程式中寫出類似這樣的程式碼:
// 找出特定作者的所有文章 $sql = 'SELECT * FROM articles WHERE author = :author'; $sth = $dbh->prepare($sql); $sth->execute(array( ':author' => 'jaceju' )); $articles = $sth->fetchAll();
大部份的人都會習慣直接這樣操作資料庫,但是如果今天我們需要不同的資料來源,那麼它就綁死在關聯式資料庫上;而且如果我們想要重複使用這段邏輯時,那麼這段程式碼就很明顯地被綁在這支 PHP 裡了。怎麼解決呢?
我們可以簡單地利用類別包裝這一段邏輯,讓它在呼叫時看起來更人性化:
// 找出特定作者的所有文章 $blog = new Blog(); $articles = $blog->fetchArticlesByAuthor('jaceju');
這麼一來,不但程式語意更清楚,我們也隔絕掉了資料庫的實作,而其他地方需要重複使用這段程式碼時,只要呼叫同一個類別方法就可以了。
寫完的程式一定會佈署到線上環境執行,但是如果程式跟系統的相依性太高時,在佈署時就會很麻煩。例如以前很多人在 include 外部檔案時,習慣把路徑寫死。
<?php include '/home/web/my/config.php';
這麼一來程式碼就只能在那個環境下才能執行。這是相當不好的做法,因為如果需要把程式碼搬到另一個位置去執行時,就很容易造成佈署上的麻煩。
好的程式架構應該要把相關的引用檔案都集中,再利用相對於目前執行檔案的路徑去載入。通常我們可以使用 DIR 這個執行時期常數來取得目前執行檔的絕對路徑,然後再從這個路徑去載入相對路徑的檔案,讓程式不再依賴系統的實體路徑。
<?php include __DIR__ . '/config.php'; // 假設 __DIR__ 在執行時期的值為 "/home/web/my"
當然這只是其中一個環節;越讓程式儘可能不相依系統環境,佈署的困難度也就會越低。
一開始做對系統純屬神話,要將系統寫活來應付變化
大家都知道,系統很難一開始就做對,所以一定會有新增的功能需求。但是每次新增程式碼時,就會讓系統變成疊床架屋,變得越來越難以維護。要儘可能把程式寫得活一些,讓新增的程式碼可以不用在破壞原有程式的狀況下被加入。
我想小時候大家都看過非常熱血的組合機器人卡通,這些機器人通常能夠組合或變形,甚至不同的機器人之間的組件也可以交換使用,為什麼呢?它們靠的就是統一的介面。
換句話說,系統要能夠靈活地被擴充的話,我們就要先將統一的介面與適當的插入點定義下來。而在分析這樣的系統架構時,或多或少都會接觸到所謂的設計模式。雖然我個人不建議一開始就把設計模式帶入程式裡,但確實有幾個簡單的 Patterns 可以幫我們完成一個靈活的程式架構,像是 Template Method 和 Observer 等。
當然不是單用這些 Patterns 就可以做出容易擴充的系統,其實還有很多其他需要考慮的部份。像是模組化的功能設計,易於使用的管理介面等,這些都是讓系統可以變得更易擴充的要素。
學習跟團隊一起開發很重要,但個人的自我要求也不可以欠缺。雖然 PHP 是個自由的語言,但不表示你就能亂寫。多數時候我們寫的程式碼可能不會讓電腦抱怨,可是如果不把安全的寫法時時牢記在心的話,就會提供駭客入侵系統的機會。
最重要的就是避免一些常見的漏洞,如果程式中沒有正確防範使用者的輸入時,那麼就可能帶來嚴重的問題,像是: SQL Injection 、 Cross Site Script (XSS) 、 Cross Site Request Forgery (CSRF) 都是不可不注意的部份。
另外也要記住「不要信任瀏覽器所回傳的任何資訊,也不要輸出任何敏感的資訊」,在 PHP 中,對輸入用 Filter 去過濾。在輸出時要記得做好 escape 。
另外 PHP 程式發生警告或錯誤時,通常會出現可以被追查的錯誤訊息,這在開發時是很有幫助的。可是到了正式環境,這些資訊就有可能會被駭客利用。所以我們在開發 PHP 程式時,一定要記得儘可能解決掉所有的警告及錯誤。而在線上環境時,要記得把這些訊息關閉,改用 log 的方式來記錄。
我想很多現在已經有很多網站服務可以應付大家日常的需求了,不過在這些網站身體裡的怪物越長越大時,就一定會遇到一個問題:那就是非常慢。
因此,網站的效能就是很多人追求的目標,而語言的效能更是被拿出來比較的重點。這點老實說 PHP 確實是有先天上的劣勢,不過這不表示 PHP 真的很差。在 PHP 5.3 及 PHP 5.4 之後,它的執行效能已經有很明顯的進步。 當然除了語言引擎的效能外,其實最大的問題還是出在我們自己過於把效能問題集中在一個點上面。
其實有時候用一些原則,就可以讓程式效能突飛猛進。以下我列了一些我自己撰寫 PHP 時常用的原則:
舉我們最常用的費式級數為例,我們最常看到的實現方式就像這樣:
function fib($n) { if ($n < 2) return $n; $f[$n] = fib($n - 2) + fib($n - 1); return $f[$n]; } echo fib(30), "\n"; // 結果: 832040 ,共執行 5.59556889534 秒
這段程式碼在我自己的電腦上執行這個程式大約需要 5 到 6 秒。但如果我在上面加上兩行程式碼:
function fib($n) { static $f = array(); if (isset($f[$n])) return $f[$n]; if ($n < 2) return $n; $f[$n] = fib($n - 2) + fib($n - 1); return $f[$n]; } echo fib(30), "\n"; // 結果: 832040 ,共執行 0.002144813537 秒
它的執行時間就剩下 0.002 秒。
神奇嗎?其實一點也不,我只是遵守了「不做重複的事」這個原則,利用 static 變數來記住已經算過的值。
所以當你越瞭解語言,就有機會用更簡單的方法來提升效能。
當然如果已經達到語言本身的瓶頸時,那就朝其他方向下手吧。在「奧巴馬籌款網站的製作過程」這篇文章裡,就提到了很多提升效能的方式。當然,他們也用到了 PHP 。
不論是你個人或是整個團隊所開發出來的程式碼,到後面都有可能越變越糟。近年來社群已經幫 PHP 開發許多軟體工程的技術與流程,讓我們的程式碼可以更加地強健。以下幾項技術都是可以幫助我們做到這件事的途徑。
當你的程式越寫越大時,不論在新增或修改功能時,就可以常會發生把舊有的功能改壞的狀況。測試就是一個能確保你不會把系統改壞的工具。而且有時候如果客戶回報 bug 時,你也可以透過撰寫測試的方式來建立問題發生的情境,進而找出潛藏在程式裡的錯誤。
測試通常需要一些自動化測試框架,在 PHP 裡最有名的就是 PHPUnit 。另外也有一派開發者提倡 TDD ,也就是測試驅動開發,它強調的是測試先行,讓程式在開發時能有測試案例做依據。不過近年來從 TDD 延伸出一個新的分支,叫做 BDD 也就是行為驅動開發。 不同於 TDD , BDD 更強調使用者在操作程式上的行為,所以開始為開發者所重視。當然 PHP 也有相關的 BDD 框架,較知名的有 PHPSpec 及 Behat 兩種。
不論我們要不要使用 TDD 或 BDD ,最重要的是透過測試幫我們的程式買好保險,這樣才能讓程式碼能夠平安長大。
當有了測試為基礎,我們就可以對我們的程式碼進行重構。重構並不是重 Go ,而是讓程式碼不會因為越寫越大而漸漸腐敗而發出臭味。重構一定要搭配測試來實行,這樣才不會因為重構而改壞了現有的功能。其他語言例如 Java 所常見的重構技巧大多都可以用在 PHP 上,所以推薦大家 Martin Fowler 的「重構」一書,一定會讓你在程式碼的整理功能大增。
雖然 PHP 沒有好的工具可以做到複雜的重構,但是現在的一些 IDE 都可以幫我們做到簡單的重構了。像是 NetBeans 就有針對 PHP 變數、方法重命名的重構功能。
不要害怕修改程式,小步的重構會讓你的程式碼更加強壯。
我想也有很多人認為身為一個 PHP 開發者,只要把 PHP 程式寫好就好,但那樣其實是不夠的。在專案越來越複雜的狀況下,其實要瞭解的知識也會越來越多。要當一個更優秀的 PHP 開發者,以下的知識都是必要的。
PHP 開發人員不但要瞭解程式運作的系統環境,更要能夠管理這個系統。基本的系統安裝、 Shell Script 、 Service 管理、排程設定等等都是必要的知識。而且越深入瞭解系統,相對的我們就能提供程式更多的解決方案。
有些 PHP 開發者是靠接案為生,以往他們會在 IDC 放置自己的機器來服務客戶,但這樣一來自己維護的成本就相對很高。所以在現在的雲端時代,就有很多平台服務供我們選擇。像是 AWS / Linode / Heroku 等服務,它們之間也存在著一些差異,在使用前一定要多去瞭解。
推薦兩篇很不錯的介紹文,雖然是 2012 年的文章,但還是非常具有參考價值。
當你越熟悉 PHP 時,同時你也會瞭解它的的極限。所以不要什麼東西都想要用 PHP 解決,其他語言也提供了很多不錯的工具。 事實上這年頭不多學一兩種語言在身上,可能就很難存活下去了。 身為一個 Web 開發者,首先最重要的是學會瀏覽器上的 JavaScript 。另外可以的話最好也能學習 Perl / Python / Ruby / Node.js 或是其平台的安裝,因為有很多不錯的 Web 開發工具都會用到這幾個語言的平台。
如果想要更進一步透過 PHP 底層來加強程式效能,那麼 C 語言的學習也是必要的。
另一個跟程式無關的語言,就是英文。很多 PHP 的新特色或新工具都是國外的網站先發佈消息的,不要害怕它們是英文所以就不去看。
還有 PHP 的一些討論資訊也非常值得注意,裡面會有很多 PHP 未來的開發走向。這裡列舉的是幾個較具代表性的網站,各位可以從這些地方再去找出更多的連結。
除了工作中所用到的工具之外,各位也可以試著用 PHP 做一些有趣的工具,然後到 GitHub 分享成果。只要能保持你對 PHP 的熱情,我相信 PHP 一定會回饋你更多。
最後,為大家介紹台灣 PHP 界的代表性人物: c9s 。他開發了很多非常有用的 PHP 工具,也使得他在 GitHub 上的活躍度是在全世界排名第二。
如果你喜歡 PHP ,那麼希望這樣的介紹能讓你對它有更深一層的認知。當然這些招式只是起手式而已,真正的功夫必須靠大家勤練才行。