分類彙整:programming

給網站初學者的建議:用 Ruby on Rails 非常辛苦,用 PHP 非常舒服

Rails是新手入門學寫網站最辛苦的選擇之一。

常常有文章或是課程建議新手用Rails入門寫網站,我認為這種建議十分危險。

Rails並不適合初學者使用。推薦沒有資訊背景的人去學Rails,很可能害他事倍功半。

新手用Rails入門的問題在哪?

Rails最大的問題就在於:它從一開始就不是設計給新手用的。

它是設計給工程師快速開發用的。

軟體工程沒有萬靈丹。適合老手的,對新手來說一定太難;適合新手的,對老手來說一定太囉唆。

Rails為了替工程師節省反覆設定(configuration)的時間,而預設了許多立場(convention)。

新手連web application的基本知識、環境設定都不知道,

直接去學前人的framework和convention,絕對不是好事,也非常辛苦。

我們從Rails的官方入門導覽來舉例吧:

http://guides.rubyonrails.org/getting_started.html

對新手來說,這份導覽真是充滿了災難。

它就跟大部份的rails教材一樣,會帶新手去學很沒必要的4件事情:

1. 新手沒必要學框架的檔案結構
2. 新手沒必要學框架的command line指令
3. 新手沒必要學helper
4. 新手沒必要學migration

1. 新手沒必要學框架的檔案結構

新手打開導覽之後,映入眼簾的,是這個介紹檔案結構的嚇人表格:

File/Folder Purpose
app/ Contains the controllers, models, views, helpers, mailers and assets for your application. You’ll focus on this folder for the remainder of this guide.
bin/ Contains the rails script that starts your app and can contain other scripts you use to setup, deploy or run your application.
config/ Configure your application’s routes, database, and more. This is covered in more detail in Configuring Rails Applications.
config.ru Rack configuration for Rack based servers used to start the application.
db/ Contains your current database schema, as well as the database migrations.
Gemfile
Gemfile.lock
These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see the Bundler website.
lib/ Extended modules for your application.
log/ Application log files.
public/ The only folder seen by the world as-is. Contains static files and compiled assets.
Rakefile This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.
README.rdoc This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.
test/ Unit tests, fixtures, and other test apparatus. These are covered in Testing Rails Applications.
tmp/ Temporary files (like cache, pid, and session files).
vendor/ A place for all third-party code. In a typical Rails application this includes vendored gems.

初學者一開始只想做個部落格或是個人網頁而已,真的有必要去面對這麼多檔案嗎?

這種架構是框架替工程師的軟體專案長遠著想,而詳細分工的結果。

新手並沒有要開發大型專案,做出幾個能跑能動的頁面就夠了。

結構或是程式碼醜一點無所謂,好上手就好,實在沒必要一開始就碰這麼複雜的。

2. 新手沒必要學框架的 command line指令

官方導覽介紹了產生controller相關檔案的指令:

$ bin/rails generate controller welcome index

然後rails會顯示以下訊息:

create  app/controllers/welcome_controller.rb
 route  get 'welcome/index'
invoke  erb
create    app/views/welcome
create    app/views/welcome/index.html.erb
invoke  test_unit
create    test/controllers/welcome_controller_test.rb
invoke  helper
create    app/helpers/welcome_helper.rb
invoke  assets
invoke    coffee
create      app/assets/javascripts/welcome.js.coffee
invoke    scss
create      app/assets/stylesheets/welcome.css.scss

只不過想做個簡單頁面,居然跑出了好幾個檔案。對新手來說,這非常不友善。

新手想要的,是在資料夾裡面一次建立一個檔案,然後在裡面寫一點東西。

接著看看程式有什麼反應、跑不跑得動。這樣他才知道自己在做什麼、在學什麼。

像這種打一個指令產生一堆檔案的學習方法,就算真的「快速做出了一點東西」,

之後大概連怎麼修改程式都不知道。如此黑箱,很容易帶來挫折感。

3. 新手沒必要學helper

官方導覽提到了Form的寫法如下:

<%= form_for :article do |f| %>
  

<%= f.label :title %>
<%= f.text_field :title %>

<%= f.label :text %>
<%= f.text_area :text %>

<%= f.submit %>

<% end %>

這完全是災難一場。

新手該學的不是這種ERB語法,而是原始HTML的Form element語法:

Form element的語法一點都不難,網路上到處找都有

新手學HTML基本知識就夠了。接著再學一些HTTP的基本知識,了解瀏覽器如何將表單的資料丟給後端程式互動。

學這些基本知識才踏實、才有成就感。

helper是工程師為了少寫HTML而使用的進階工具。

初學者連基本的HTML都不會,學什麼helper?徒然增加挫折感而已。

4. 新手沒必要學migration

migration是工程師方便管理資料庫結構的版本,以及在團隊成員間同步化的工具。

初學者根本沒有這種管理資料庫的需求。

新手如果要學資料庫操作,應該去學資料庫軟體(譬如說MySQL)本身的語法,這樣才會有學到資料庫操作的扎實感覺。

如果是一開始不想學資料庫語法的新手,那更不應該去學migration。

他應該去安裝一款有圖形化介面的資料庫管理軟體,用滑鼠按一按把資料表做出來就可以了。

初學者最舒服的學習路徑:PHP + 懶人包

初學者對於開發網站的想像,不外乎就是「寫幾行程式碼,做出幾個小頁面」。

他一開始對於環境安裝與調整沒有興趣,也負荷不來。

這個時候,使用PHP搭配懶人包開發會是最舒服的路徑。

學寫網站至少要碰幾個東西:

  • HTML
  • CSS
  • client-side programming (JavaScript)
  • server-side programming (ex: Ruby on Rails 或是 PHP)
  • 資料庫
  • 架server

我對新手學習的建議是這樣的:

1. HTML和CSS幾乎沒有入門門檻,線上隨便找教材都有。
2. JavaScript主要是用來做瀏覽器上的一些動態效果,非必要。一開始先不學。
3. 資料庫跟架server一開始超出新手想像,可以先安裝懶人包帶過。
4. server-side programming才是新手原本理解的「學寫網站」。

基於以上四點建議,新手一開始把心力放在1、4即可,然後從以下懶人包擇一安裝:

這些懶人包會直接幫你裝好server、資料庫與PHP。

資料庫相關的操作可以先透過phpMyAdmin之類的軟體用滑鼠操作。

接著在線上找PHP教學之類的東西,以「一個檔案就可以做出一個網頁」的方式開始學習。

在懶人包的協助下,按照興趣慢慢摸索HTML, CSS和PHP的知識。

之後再根據目標,從以下三個方向更進一步學習:

  • 急著讓網站上線者,可以把程式碼交給工程師朋友,請他幫忙買網址、架起來。
  • 對server side工程有興趣者,可以補強資料庫、伺服器等等知識。試著不依靠懶人包就獨立搞定環境、挑一個框架學習之類的。
  • 對client side工程有興趣者,可以補強JavaScript的知識,學習前端相關知識。

入門最重要的是循序漸進,先想辦法做出一個能動的網站,

之後再根據需求,把缺少的技能一個一個補起來。

在「實作出成品」與「學習新知識」之間來回進行,反覆累積成就感與能力。

一口氣全部一起學,只會非常茫然、倍感挫折而已。況且根本沒有必要。

用Rails就更慘了,在一開始就得學一些工程師用的進階手法。

總結起來,我給網站初學者的建議就是:用 Ruby on Rails 非常辛苦,用 PHP 非常舒服。

(Photo via Per Gosche, CC licensed.)

[推薦]學寫程式的幾本書籍

常常被朋友問到,說他工作上有某某需求,想學某某語言,有沒有適合他的書本?

有的,我的確有推薦的入門書籍。

這裡寫文章一次回答這些問題。

Q: 我沒有任何基礎,想入門程式設計,希望能叫電腦替我做些事情?

我推薦「深入淺出程式設計」。
programming
Q: 我沒有任何基礎,想入門程式設計,學架blog、網站、個人網頁?

我推薦「深入淺出 PHP 與 MySQL」。
php-mysql
Q: 我不想學寫程式,只想學設計相關的HTML&CSS?

我推薦「深入淺出HTML&CSS」。
html-css
Q: 我有一點基礎,但想更了解Python的初級、中級觀念?

我推薦「深入淺出 Python」。
python
Q: 我有一點基礎,但想更了解Java的初級、中級觀念?

我推薦「深入淺出 Java 程式設計」。
java
Q: 我有一點基礎,但想更了解JavaScript的初級、中級觀念?

我推薦「深入淺出 JavaScript」。
javascript

你可能會覺得奇怪,怎麼全是同一套系列的書?

這也沒辦法,因為深入淺出系列就是如此出色。

整套系列,我買入手的、曾經去圖書館借來看的,就在10本以上。

歐萊禮的這系列書籍,跟一般的電腦書籍非常不同,

它會兼顧你學習、應用、樂趣、知識,多個入門最需要的面向。

試了很多本電腦書籍,卻依然一頭霧水的朋友,不妨試試看深入淺出系列。

先寫單元測試的12個好處!

「寫測試的好處是什麼?」

這個問題非常難回答。通常只能得到「只有寫了才知道」這種含糊不清的答案。

我最近找到Tim King在2006年的文章,非常完整的回答這個問題。

看過之後覺得獲益良多,翻譯出來和大家分享。


先寫單元測試的12個好處!

為什麼工程師會討厭寫測試?為什麼他們會拒絕先寫測試?不用解釋了,那些藉口我全聽過。我知道真正的原因為何。

大部份的工程師根本沒認真試過測試先行。不然就是當下的環境不支持他們寫,導致他們不知道自己在幹嘛。前者的情況居多。最後他們就找藉口:「我們沒時間寫單元測試」、「單元測試不能完全保證程式碼品質」。這是在替自己的悲慘找理由,而不是真的覺得不寫比較好。有趣的是,當這種人直接在你面前開發,你常會看到他開發得很不順,進度常常在開倒車。目睹這種事滿有趣的。但他依舊不會收回原本的看法、依舊不願意先寫測試。這種堅持根本有害。

Kent Beck在《Test Driven Development By Example》書中提到,測試先行有3個步驟:

1. Red:寫個能表達你打算如何使用那段code的測試,還有你期待它做什麼。這個測試會失敗。很多介面會用紅色訊息來表示它。
2. Green:寫出足夠的code來讓那個測試成功,但別多寫。如果你想寫更多code,像是檢查某些錯誤的話,那就先另寫一個測試表達它。當下只要寫剛好夠的code去通過測試即可。
3. Refactor:把多餘的code清理一下,然後改善整體設計。之後再跑一次測試,確保沒弄壞什麼地方。

重複這些步驟直到功能做完。這個流程超級簡單。為什麼工程師會畏懼它?因為這會逼他們從根本上改變開發習慣。

我們認為自己在寫code前不需要先去思考,這些code到底要做什麼。

你如何解決一個軟體問題?學校是怎麼教的呢?第一步怎麼走?你大概只想著如何解決問題本身。你心想:「我要寫哪些code來實作出解決方案?」其實你不應該先想「我要寫哪些code」,你要先想「我要怎樣才能確定問題已經被解決了?」

我們直覺認為一段code的正確與否,只要執行一次就知道了,超明顯的,何必寫那種根本廢話的測試?就是這種這種根深蒂固的想法,導致大部份的人改不了開發習慣。

成功跨過那道鴻溝的人,可以感受到下列幾項好處。我全部體會過。不用完全信我,你自己試試看就知道了。

1. 單元測試保證你的code真的能動

這會讓bug減少。當然,單元測試不能取代系統測試跟驗收測試。但單元測試能補足它們的短處。

2. 你會得到一組底層的regression-test suite

這讓你隨時可以回頭去檢查有否哪邊壞掉、bug在哪。很多團隊會每天把整組測試跑一次。這讓你在把程式交給品管部門之前,可以很輕鬆的把bug抓出來。

3. 讓你改善系統設計的時候,不怕弄壞系統

其實就是測試先行3步驟的第3步。通常測試先行寫出來的code不太需要重構。我看過很多超糟糕的系統,就像精神病患一樣,根本無法搞定。如果有準備好單元測試,你就可以對系統裡面最難搞的部份做出有效的重構。

4. 寫測試會讓coding更好玩

你會先搞懂自己的code要做什麼。然後再讓它完成任務。就算系統還沒全做完,你還是能看到code真的動起來,而且真的沒出錯。你會得到一種「我完成了!」的感覺。每分鐘都會不斷感受到喔。只要試試測試先行,你就會整個人high起來、對自己的作品感到驕傲、被激勵去完成更多事情。

5. 它們可靠地展現目前進度

你不用為了等整個系統組裝起來而多等一個月。在系統完成之前你就能展示進度了。不但能說自己寫了code,還能真的跑給別人看。傳統開發有件事搞錯了。「完成」不等於你寫了code然後丟出去。「完成」應該是你的code能在系統裡跑,而且沒bug。寫測試會讓你更接近這點。

6. 單元測試是一種使用範例

我們都碰過那種不知道怎麼用的library。通常我們會先去找範例程式碼。使用範例可算是一種文件。但公司內部的code通常不會有範例可看。所以只好慢慢試、在系統內東找西找了。因為那個同事可能根本離職了,想問他都沒辦法。單元測試可以當作一種文件。當你不知道Foo類別怎麼用,去看一下單元測試怎麼寫的即可。

7. 測試先行會強迫你寫code前先做規劃

先寫測試會逼你在動手開發前把必須完成的事和整體設計想過一遍。不但讓你更專注,還能讓設計更漂亮。

8. 先寫測試能減少bug的成本

越早發現bug越容易修。之後出現的bug通常是改了好幾個地方才出現的,
導致很難抓出哪裡導致了bug。一開始先找出bug在哪,然後要重新回想這段code是怎麼寫的,因為可能是幾個月前寫的。最後才終於弄懂,搞出一套解法。只要能減少抓bug以及修好bug的時間,幾乎都算大賺。如果在成品交給品管部門或是顧客之前,我們只花幾天就找出bug,通常算是很幸運。那幾花幾分鐘就找出bug呢?測試先行就能做到這點。

9. 它比代碼檢查的效果好

有人說事前代碼檢查比事後測試系統更好,因為成本比較低。在系統完成之後才測試系統,要修好bug可說是麻煩多了。越早發現bug,就越簡單、越便宜、越好搞定。代碼檢查的好處就在這:只花幾天就能抓出bug,不需要等幾個月。但是測試先行成本更低。只要幾分鐘就抓出bug,連幾天都不用。

10. 幾乎解決了「開發者瓶頸」(coder’s block)

不知道下一行寫什麼嗎?就跟「作家瓶頸」(writer’s block)一樣,開發者瓶頸很可能是個大問題。測試先行有系統地處理開發上關於結構的部份,讓你能專心在需要創造的部份。你可能會卡在下段code不知道怎麼測、該怎麼通過測試,但你永遠不會因為下一步卡住。通常會有完全相反的結果:你很想在累倒之前休息一下,但因為清楚看到前面的錄了,所以根本不想停下來。

11. 單元測試讓設計更棒

測試一小塊code會強迫你定義清楚那段code負責什麼。如果測起來很簡單,就表示它的責任很明確,cohesion很高。如果一段code能被單元測試,那就表示它很容易就能放進系統之中,就跟它很容易放進測試之中一樣。它跟相關的code只具有loose coupling 。 High cohesion與loose coupling代表了出色、好維護的設計。容易測試的code也很容易維護。

12. 寫測試會讓開發速度更快

不寫單元測試也許會讓速度更快,但無法保證code真的能跑。開發上會花一堆時間在在事後的修bug。測試先行會消除這類的浪費,從一開始就做對、讓bug更好修。

就算好處這麼多,很多工程師還是繼續維持他們的老樣子。如果你在組織裡極度重視流程,你跟他們一部份人會起衝突。我只能祝你好運。記住一件事,人們不會因為一個東西聽起來不錯就買帳。他們只有在極度渴望、超想得到手來品嚐時才會買帳。希望以上幾點可以幫助你說服他們。

不過,如果你是前者,也就是那種頑固的工程師,不在乎好的軟體設計,只在乎堅持己見…。嗯,我覺得你還真可憐。

(Photo via 8#X, CC licensed)

PHP與撰寫測試入門

許多人對於撰寫測試躍躍欲試,卻因為PHPUnit等工具太複雜、

相關名詞艱澀難懂而不知該如何開始。

其實PHPUnit這類測試工具並非必須品,相關專有名詞也可以先擺一邊,

只要發揮創意想辦法測試就是第一步了。

我們從最簡單的形式開始談起吧!

假設我們有一個簡單的加法程式要測試


最簡單的測試其實只要這樣寫


要執行測試非常簡單,只要打開terminal然後輸入

php simple_add_test.php

就可以看到測試結果了!

PHP Parse error:  syntax error, unexpected '{' in /home/howtomakeaturn/projects/testing-tutorial/simple_add_test.php on line 10

突然發現if打成了$if,修正之後再次執行的結果是

PHP Parse error:  syntax error, unexpected '}', expecting ',' or ';' in /home/howtomakeaturn/projects/testing-tutorial/simple_add_test.php on line 12

發現少打一個分號,修正之後再次執行的結果是

PHP Parse error:  syntax error, unexpected '';' (T_ENCAPSED_AND_WHITESPACE), expecting ',' or ';' in /home/howtomakeaturn/projects/testing-tutorial/simple_add_test.php on line 13

發現多打一個單引號,修正之後再次執行的結果是

simple_add_test passed

終於通過測試了!

注意到了嗎?光是替這樣簡單的程式撰寫簡單的測試,就可以省下不少開發時間:原本要打開瀏覽器重新整理三次才能修完bug(人工測試3次),變成只要在terminal跑三次就修完bug了!

Q1: 打開瀏覽器重新整理三次其實沒花多少時間...寫測試真麻煩,而且不划算。

有人說寫測試一定划算,也有人說寫測試一定不划算,其實都是不對的說法!
某些情況下確實不用寫測試,光靠瀏覽器就人工測試完成了。
但很多時候人工測試的時間會大於撰寫測試的時間。譬如說一個需要先登入、輸入一串假資料、東點西點按鈕才出現結果的頁面。那種時候,寫測試就會划算了。

Q2: 二加三等於五是什麼爛測試...,也太不嚴謹了吧。

這類稱之為happy path的測試確實非常隨便。但是以入門來說,光寫這類測試其實已經有很出色的測試效果:

1. syntax error全部會被找到
2. 至少確認happy path通過了。很多時候程式出錯就是連這種happy path都過不了

不過,為了謹慎起見,我們來寫更完整的測試吧!


接著在terminal輸入

php simple_add_better_test.php

會看到結果顯示

simple_add_test passed
simple_add_test passed
simple_add_test passed
simple_add_test passed

恭喜,通過更嚴酷的測試了!

Q3: 老實說你的測試還是很爛...只是拿1跟2跟0跟-1隨便測一下而已,實在不能保證什麼。

沒有錯,其實軟體測試從來就無法保證軟體不會出錯。
有人說軟體測試是為了讓程式正確執行,其實不太正確!
軟體測試只是為了讓你對程式有信心而已。
要測到什麼程度則取決於:

1. 你心臟有多大顆
2. 這個程式值得你花多少時間去測到何種程度

以你的高標準來說,可以再拿999,999,999跟-999,999,999,999,999,999等數字去測,甚至拿null、空字串、亂數字串丟進去測(如果你覺得有意義的話)。

很不幸的,大部份的程式不會像上面那樣單純顯示結果,而是會包含一堆html。
像是一個新增文章的程式,使用者會從表單填寫標題與內容,接著以HTTP POST送出。


Title:

像這樣結果複雜的程式,該怎麼測試呢?

嘿嘿,還記得一開始說的「只要發揮創意想辦法測試」嗎?

我們來檢查最後結果有沒有包含特定字串吧!


接著在terminal輸入

php blog_submit_test.php

會看到結果顯示

blog_submit_test for title passed
blog_submit_test for content passed

奇妙吧!根本還沒打開瀏覽器,就幾乎可以確定這個新增文章的程式沒問題了!
不但syntax確認沒問題,也很有信心會正確新增文章!

Q4: 這個測試未免也太爛了。一看就知道bug一堆。要是測試標題輸入「Title」不就會抓到h1裡面那個「Title」?那就是根本沒測到!另一個測試「I am happy」更扯,如果頁面其他地方本來就有字串包含「I am happy」,不就毫無意義嗎?

說得真好!不過Q3已經說了,軟體測試只是為了對程式有信心而已。
雖然只寫了兩個破爛測試,但我已經對程式很有信心囉!我認為OK的!
而且不是說了要發揮創意嗎?不然我還能怎麼測呢?

好吧,其實會這麼鳥是因為呈現邏輯(presentation logic)和商業邏輯(business logic)混在一起的關係。
把business logic獨立出來測吧!

先把business logic獨立出來放進另一個檔案,包進function內


再改一下原本程式


Title:

測試則改寫成


接著在terminal輸入

php blog_submit_v2_test.php

會看到結果顯示

blog_submit_test for title passed
blog_submit_test for content passed

酷吧!你提到的「頁面其他地方本來就有字串包含『I am happy』」的bug解決了!
這下是不是對程式更有信心了?
除此之外,現在測試裡面不再硬塞value給$_POST變數,也不再需要output buffering的技巧了。
事情更單純了吧。你常聽到有人說測試要一小塊一小塊測,就是這麼一回事。

Q5: 不對喔...我發現事情不對勁。這下你的測試根本沒include到blog_submit_v2.php這支檔案!檔名根本不配blog_submit_v2_test.php。變成只測到business logic而沒測到presentation logic了。

我測完business logic其實已經很安心了,至於presentation logic的話我會直接打開瀏覽器,頁面正確顯示我就安心了。你真要那麼教條化嗎?好吧那你再寫一個測試去include blog_submit_v2.php這檔案好了。(寫那種測試的時間真的值得嗎?直接開瀏覽器檢查不好嗎?)
但是我同意blog_submit_v2_test.php這命名有問題。就改成add_blog_to_db_test.php吧!

Q6: 等一下!我發現你的範例跟測試都是個笑話!你把add_blog_to_db函式最重要的任務「資料庫存取」省略了!回傳那array根本搞笑,真的存取資料庫是要怎麼測試?你倒是說說看。

老話一句,發揮創意吧。把mysql函式寫得不成功便成仁就是一種方式

if (mysqli_query($conn, $sql)) {
    return [$title, $content];
} else {
    die("Error: " . $sql . "\n" . mysqli_error($conn));
}

或是最後回傳true之類的,讓測試程式能夠檢查就好。發揮創意吧,發揮創意吧。

讀到這邊,相信你已經發現一件事了:最傳統的一個php檔案寫到底的寫法,非常難以測試。就算測了心裡也很不踏實。聽過有人說「寫測試不難,難的是寫出能被測試的程式」嗎?

以後不要再寫像那樣老舊的php code了,建議把重要的business logic全部包成獨立的function,然後再分別測試function比較好!

除此之外,前面只用到==運算子和strpos函式而已,如果想寫出更多種的測試,光是內建函式就有這些可以用喔
is_​array
is_​bool
is_​callable
is_​double
is_​float
is_​int
is_​integer
is_​long
is_​null
is_​numeric
is_​object
is_​real
is_​resource
is_​scalar
is_​string
很酷吧!記得,重點是發揮創意,寫出能被測試的code、從最基本的測試開始做起!

有測試總比沒有好!

Q7: 我發現盲點了。寫測試說是要節省開發時間,但是光跑測試本身我就要輸入

php simple_add_test.php
php simple_add_better_test.php
php blog_submit_test.php
php blog_submit_v2_test.php

這東西四次!想想看,每次增加新功能或是修改舊功能,為了確保全部程式都正確,我要重新輸入這東西四次!光是打字我就飽了,哪有更省時間?

這個問題很好解決,寫一個測試主程式就好啦


以後只要在terminal輸入

php test_suite.php

會看到結果顯示

simple_add_test passed
simple_add_test passed
simple_add_test passed
simple_add_test passed
simple_add_test passed
blog_submit_test for title passed
blog_submit_test for content passed
blog_submit_test for title passed
blog_submit_test for content passed

光是這麼無聊的範例,看到9個測試都通過就很有成就感了,正式上線的application那種幾百幾千個測試通過的感覺更充實、更對系統穩定性有把握。

每次增加新功能或是修改舊功能,為了確保全部程式都正確,記得都跑一次php test_suite.php喔!再也不需要打開瀏覽器點來點去半天了!

結語

這篇文章是對測試基本觀念的說明,實際開發時使用PHPUnit或是其他測試框架,在做的事情其實跟本文差不多。

如果認為PHPUnit太複雜的話,可以先從比較簡單的測試工具開始,像是SimpleTest就不錯。

至於所謂嚴謹的測試技巧,以及物件導向的開發測試手法,我們下次再談吧!


社群feedback(last updated: 2015-10-14)

本文在PHP台灣FB社群引起一些討論與檢討,

推薦大家一併延伸閱讀

討論串一

討論串二

這樣寫測試錯了嗎?


如果您喜歡我的文章,可以在這裡訂閱。我有新想法的時候,很樂意跟你分享。

本文的程式碼可以在這裡取得。

(Photo via Masahito Oku, CC Licensed.)

寫程式不需要天份,也不需要熱情

從來沒有一個技能,曾經被神化到這個程度:

「你不但要有天份,還要有熱情,才適合寫程式。」

那些寫程式的人,好像「從小就立定志向,決定未來要寫程式了」。

缺乏其一的話,你要嘛是個假貨,要嘛走不遠,總之就是不適合。

這種深植人心的刻板印象不但大錯特錯,同時還是有害的。

隨便找幾個工程師都能證明這點。

Jacob Kaplan-Moss(Django創造者)

Jacob Kaplan-Moss的這份簡報提到:

一個平庸工程師的自白

這種關於「程式天才」的神話非常有害,一方面它把行業門檻設置得特別高,令很多人望而卻步,另一方面它也在折磨產業內的人,因為你如果不能 rocks ,就會變成 sucks ,所以不得不用一切時間來努力學習和工作,導致影響生活。…(略)…我們應該改變這種態度,寫程式只是一些技能,並不需要太多天分,它是可以學習的,而且做一個平庸的工程師不丟人,

他本人在Twitter的自介直接寫「不是真的程式設計師(not a real programmer)」,

透漏著他對這種迷思的不耐煩。

Jacob Thornton(Bootstrap作者)

在Github擁有八萬顆星的Bootstrap作者,

前Twitter、現任Medium工程師Jacob Thornton的一篇採訪也是這種迷思的反例:

Jacob Thornton痛恨電腦(Jacob Thornton Hates Computers)

當他說「我痛恨電腦」的時候,並不完全在開玩笑。…(略)…他說「我本來要去唸社會學的」

接著描述了他第一份工作的情況:

我拿到了一個遠超我能力的工作。每一天都可能被開除。所以我非常努力工作,想搞懂JavaScript,因為我不懂它到底在幹嘛。

我一生中最現實的一刻到了。整間公司的人圍在我身邊,要我做一個XHR request。我根本沒做過,我只稍微聽過而已。於是我開始打字、重新整理瀏覽器,然後什麼都沒出來。我反覆做了幾次,知道自己完蛋了,他們發現我是假貨了。接著我突然發現自己忘記加「.send()」。我加了之後再次重新整理瀏覽器,畫面成功顯示。整個團隊感覺像在說「喔,酷。」然後就各自回辦公桌了。

我在那裡坐了15分鐘。心想,就這樣。我搞定了。我不會被開除了。

這段描述一點也不像「程式天才」在職場的表現。

至於支持他一路走來的動機是什麼呢?他說:

我是一個高度在乎同儕的人,我做前端的朋友總是會告訴我哪個地方做很醜或是在哪個瀏覽器上壞掉。感覺真的很棒。我真的只想跟朋友一起寫程式,一起工作。

他本人的Twitter自介寫「computer loser」,

置頂推文是「公司裡第一爛的工程師,但是第三酷」。

這種態度跟刻板印象完全相反。

Rasmus Lerdorf(PHP之父)

Rasmus Lerdorf的言論常常引起廣泛爭議:

  • 我其實很討厭寫程式,不過我喜歡解決問題。
  • 有些人熱愛寫程式。我不懂他們為何會這樣。
  • 我不是一個真的工程師。我把東西弄一弄,弄到能跑之後就不管了。真的工程師會說「這段程式能跑,但記憶體沒管理好,我們來修好它」。我只會說,一直重新開機不就好了。

從他的言論,很難看出他對電腦本身有多少熱情。

他也跟Jacob Kaplan-Moss以及Jacob Thornton一樣,懶得對寫程式的迷思多做解釋,

乾脆直接說自己是loser、假工程師了。

David Heinemeier Hansson(Rails之父)

DHH在接受Big Think訪問時提到:

說來有點好笑。我以前寫PHP跟Java的時候,常常花時間去摸其他程式語言。到處摸看看其他程式語言…隨便什麼都好。寫PHP跟Java實在太悶了,我需要用這種方式讓自己暫時抽離。

我以前寫PHP跟Java的時候,完全不覺得自己之後會當程式設計師。

整段看起來都不像是一個「電腦天才」的自我介紹。

最後讓他愛上的不是電腦本身,而是Ruby程式語言的優雅性。

如果Ruby沒有被發明,DHH現在也許會做完全不同的事情。


這一類可以說明刻板印象大錯特錯的文章實在太多了,

看看工程師們最愛的幾個玩笑:關於工程師 59 條搞笑但卻真實無比的語錄

  • 一個人寫的爛軟體將會給另一個人帶來一份全職工作。
  • 傻瓜都能寫出電腦能理解的程式,優秀的工程師寫出的是人類能讀懂的程式。
  • 開發軟體和建造教堂非常相似——完工之後我們就開始祈禱。

如果工程師都很有天份跟熱情,這些笑話又怎會受歡迎呢。

再看看Medium上很受歡迎的學習系列文章:資深開發者給後輩的七個 Coding 學習心得

其中的幾個建議

  • 也許常常有人說你是錯的
  • 也許常常會有人跟你說「你並不是個 Coder」
  • 不要在意外表,能力才是一切

無非就是想打破這類寫程式的迷思、無意義的資格論神話。

下次又有人學到一半,開始反省自己適不適合、夠不夠資格的時候,

我只想跟他說:你就多找幾種方式學學看吧,不要抱持那種奇怪的資格論。

很多時候其實只是搞錯方法搞錯心態而已。

真的完全學不懂再放棄吧。

寫程式不需要天份,也不需要熱情。


如果您喜歡我的文章,可以在這裡訂閱。我有新想法的時候,很樂意跟你分享。

(Photo via Sano Rin, CC licensed.)

程式設計第一課:對該死的蠢電腦保持耐心

最近想學寫程式的人很多,我身邊也常常有朋友問我怎麼開始。

許多文章在談選擇工具、程式語言的注意事項。

我認為最重要的注意事項是:電腦其實很笨,和它互動需要保持耐心。

不提醒這件事的話,初學者一碰上挫折會以為自己很笨,以為「自己不適合寫程式」。

一般人常覺得寫程式就是對著電腦打一堆神奇英文,像在施展魔法一樣,只有厲害的人能做。

其實完全不是這麼回事。程式設計比較像是一直看到空白畫面、錯誤訊息,出錯、設法解決、再出錯、再設法解決、結果又出錯。不斷來回循環,慢慢取得進展而已。這就是大家常聽到工程師在講的「trial and error」。

沒跟初學者提醒這件事情,會導致他看到空白畫面就覺得挫折,看到奇怪的英文錯誤訊息就想放棄。

有些人甚至已經寫出一點東西、根本已經順利上路了,卻因為太常看到錯誤訊息就覺得「自己不適合寫程式」。

這真是誤會大了。coding本來就是用這種鳥方式在跟電腦互動。

這樣說可能還是有點籠統。那跟大家分享四個入門小訣竅吧!

訣竅一:至少要看到錯誤訊息

不管你在寫人生第一個小程式、架伺服器、還是連接資料庫,失敗之後至少要看到錯誤訊息,才知道找答案的方向。

找找看去哪邊設定,讓錯誤訊息顯示在螢幕上或是紀錄在某個檔案裡吧!

訣竅二:大量Google就對了,再不行就發問

初學程式設計會遇上一堆問題,這個時候只要不斷Google就可以解決大部份的問題(通常拿錯誤訊息去Google就有答案了)。

不要因為自己「一直在Google找答案」就覺得「自己不適合寫程式」。就算是軟體工程師依然每天在Google上找來找去。

還是解決不了,就找批踢踢看板、臉書社團之類的地方發問吧!

訣竅三:遇到瓶頸,就降低目標,從簡單的開始

不用急著把書上的範例、網路上的範例一次做對。

想在介面上做出一個按鈕卻不斷失敗嗎?那就先在介面上顯示幾個文字試試看,也許會發現別的地方弄錯了。

想把一串資料在程式間傳來傳去卻不斷失敗嗎?那就先讓它們互傳幾個數字試試看,成功了再繼續往下走吧!

訣竅四:隨便瞎搞就可以了,有興趣的部份再鑽研

初學而已,你想做的事情有完成即可。

過程中許多部份會讓你覺得很「黑箱」,覺得自己沒有全都搞懂,好像在學假的?

大可不必這樣想。你不需要在一開始就弄懂每個細節。在瞎搞中獲得樂趣與成就感即可,一些技術原理和細節,有空再慢慢查吧!

總而言之,看到錯誤訊息不要緊張,因為那其實是電腦在找你聊天呢~(溫馨❤)

不要把錯誤訊息和空白畫面視為挫敗的一種。把它視為和電腦互動的方式才對。

不相信的話,去問問身邊當工程師的朋友,問他們一天在螢幕上看到錯誤訊息幾次。

(Photo via Sano Rin, CC licensed.)

胖胖 Model 的減重方法:Form

Web application常常需要對使用者輸入的參數做驗證。

這些驗證(validation)logic該放哪裡呢?

現代框架通常會提供工具協助這件事,

以Laravel來說,使用內建的Validator可以在controller內這麼做:

$validation = Validator::make(
    array(
        'name' => Input::get( 'name' ),
        'email' => Input::get( 'email' ),
    ),
    array(
        'name' => array( 'required', 'alpha_dash' ),
        'email' => array( 'required', 'email' ),
    )
);
 
if ( $validation->fails() ) {
    $errors = $validation->messages();
}

同樣的validation logic只出現一次還好,出現在不同controller就會導致這段code duplicate。

該怎麼辦呢?

試試看放進model:

class Ball extends Eloquent
{
    private $rules = array(
        'color' => 'required|alpha|min:3',
        'size'  => 'required',
        // .. more rules here ..
    );

    public function validate($data)
    {
        // make a new validator object
        $v = Validator::make($data, $this->rules);
        // return the result
        return $v->passes();
    }
}

controller內的code就會簡化成這樣:

$b = new Ball();

if ($b->validate(Input::all())){
    // success code
}else{
    // failure code
}

還算OK的作法…如果你能接受將validation logic視為business logic的話。

這類的code有比controller和model更適合的地方。寫一個form class即可:

class ArticleForm
{
    protected $validationRules = [
        'title' => 'required',
        'content' => 'required',
    ];
    protected $inputData;
    protected $validator;

    public function __construct($input)
    {
        $this->inputData = $input;
    }

    public function isValid()
    {
        $this->validator = Validator::make($this->input, $this->validationRules);
        return $this->validator->passes();
    }
    
    public function getErrors()
    {
        return $this->validator->errors();
    }

}

controller內直接呼叫即可,看起來readable許多:

$form = new ArticleForm( Input::all() );
 
if ( ! $form->isValid() ){
    return Redirect::back()->with( [ 'errors' => $form->getErrors() ] );    
}
 
$article = new Article( Input::only('title', 'content', 'status') );
 
$article->save();

如果多個model都有form classs的話,可以將共用的code抽出來成為抽象類別:

abstract class FormModel
{
    protected $validationRules;
    protected $inputData;
    protected $validator;

    public function __construct($input)
    {
        $this->inputData = $input;
    }

    public function isValid()
    {
        $this->validator = Validator::make($this->input, $this->validationRules);
        return $this->validator->passes();
    }
    
    public function getErrors()
    {
        return $this->validator->errors();
    }

}

這樣原本的form class可以簡化到只剩下validation rules:

class ArticleForm extends FormModel
{
    protected $validationRules = [
        'title' => 'required',
        'content' => 'required',
    ];
}

甚至能寫多種validation rules給不同controller:

   protected $happyArticleValidationRules;
    protected $angryArticleValidationRules;
    protected $funnyArticleValidationRules;

    public function isValidHappy()
    {
        $this->validator = Validator::make($this->input, $this->happyArticleValidationRules);
        return $this->validator->passes();
    }
    
    public function isValidAngry()
    {
        $this->validator = Validator::make($this->input, $this->angryArticleValidationRules);
        return $this->validator->passes();
    } 
   
    public function isValidFunny()
    {
        $this->validator = Validator::make($this->input, $this->funnyArticleValidationRules);
        return $this->validator->passes();
    }

實作上可以參考Laravel官方論壇原始碼

(注意:若您是用Laravel 5,Request的封裝可以取代這件事。)

下次你的application包含大量validation logic時,不妨試試看這個作法。

(Photo via Susanne Nilsson, CC licensed)