dog

搞懂為何設定 React、JSX、ES2015、Babel、Webpack 的學習筆記

學習 React、JSX、ES2015、Babel、Webpack 等等現代化的前端開發觀念時,會需要安裝很多工具、設定多個檔案、用到許多指令。

大部份教學文章只講「怎麼設定這些」而沒說明「為何要設定這些」。照著做完感覺很不放心。

最近我試著搞懂「為何要設定這些」,跟大家分享一下這個逐步搞懂的過程。

前言

這篇文章假設你想要開始用 React 開發,希望搞懂相關的前端環境與設定。

如果你想知道用 React 到底有什麼好處,可以參閱以下連結:

Introducing JSXThinking in React簡單聊一下 ONE-WAY DATA FLOW、TWO-WAY DATA BINDING 與前端框架

接下來,這篇文章會以試著寫一個陽春的 App 元件作為範例,內含 ChildA 跟 ChildB 兩個元件。

讓我們開始吧!

第一步:直接在 html 檔內開發 React 給瀏覽器執行

我們先用原始的開發方法硬搞看看!

用 script 標籤直接從 CDN 引入 react.js 跟 react-dom.js 檔案。

然後試著寫一個陽春的 App 元件,內含 ChildA 跟 ChildB 兩個元件。

這個 html 檔內容會像這樣:

source code

結果瀏覽器無法執行 React 相關的程式碼!

原因有兩個:

1. 瀏覽器看不懂 ES2015 的語法(class、extends)

2. 瀏覽器看不懂那個像是把 html 寫在 js 裡面的 JSX 語法

該怎麼辦呢?

第二步:用 Babel 讓瀏覽器能看懂 ES2015 與 JSX 語法

我們可以使用 Babel 套件來來編譯 ES2015 與 JSX 的程式碼,讓瀏覽器能夠看懂。

只要在 html 內加上這行:

然後在要編譯的 script 加上 type=’text/babel’ 即可:

就可以順利執行我們的 React 程式了!

source code

您可以用這種方式開發一些小型的 React 程式。

但實務上這些元件會複雜、龐大很多,全部寫在HTML裡面的話,不但很難維護,還有 babel 每次都在瀏覽器內編譯的效能問題。

只有3個元件可能還好,但要是有30個元件呢?

拆開成獨立檔案,並且預先用 babel 編譯過比較好。

第三步:把檔案拆開,並且預先編譯完成

把3個元件各自獨立成檔案,也把啟動這些元件的程式碼獨立成一個檔案:

接著來預先編譯它們。

這次我們不使用上面瀏覽器版本的 babel,我們用命令列介面的 babel-cli 來編譯。

先安裝好 babel-cli:

然後 babel-cli 從版本 6 之後預設不支援 ES2015 與 JSX,需要分別加裝所謂的「preset」才行:

接著就能在命令列使用 babel 並且設定 presets 來編譯了。

我們有4個檔案,所以分別打編譯指令4次:

成功編譯之後,就不需要在瀏覽器內引入 babel 了,直接使用編譯完成的檔案即可:

source code

然而,這樣做卻又產生3個新的問題:

1. 有N個檔案,就要打編譯指令N次,太累了

2. 透過 script 標籤引入N個檔案,會增加伺服器負擔、也會增加用戶端等待時間

3. babel 相關的程式碼都移出瀏覽器了,react.js 跟 react-dom.js 卻還是在瀏覽器內引入

第一個問題還算簡單,可以改成輸入指定資料夾的指令,就可一次編譯:

但是第二三個問題就棘手得多。

有鑑於此,需要找方法一次解決這三個問題:設法一口氣全部編譯、封裝成單一檔案、並且函式庫統一用NPM在伺服器端安裝與管理吧!

第四步:使用 webpack 來解決這些整合問題

webpack 會利用 babel-loader 套件來呼叫核心的 babel-core,所以先分別安裝它們:

我們不會透過命令列去使用 babel 了,既然用不到就先把它移除吧:

我們也不會用 script 標籤引入 react.js 跟 react-dom.js 檔案了。一口氣全部編譯時會用到,所以用NPM安裝它吧:

前面說過 babel 版本 6 以後預設不支援 ES2015 與 JSX,現在不透過命令列設定 presets 的話,就需要建立 .babelrc 檔案去開啟支援:

然後為了讓 webpack 在一口氣全部編譯成一個檔案時,能看懂檔案跟套件之間的相依性,因此需要按照 ES2015 的 import/export 語法寫清楚。

例如原本的 App.jsx 就要改寫成這樣:

src 資料夾內的檔案都要進行改寫。

最後建立 webpack.config.js 來設定檔案路徑,並指定使用 babel-loader 來幫忙編譯:

就可以透過命令列執行 webpack了:

這段指令有點長,可以把它放進 package.json 的 scripts 欄位:

之後就可以用下列指令來進行編譯工作了:

會得到我們要的結果:一個檔案搞定一切!

source code

大功告成!終於搞定我們想要的所有效果!

結語

本篇文章提到的內容,根據不同的套件版本,實務上有多種方式去安裝、設定達到同樣效果。

但是核心觀念是共通的!

可以此為基礎,探索更多的現代前端開發方法,學習更多工具的功能與設定!

(完)

(Photo via 8 Kome, CC licensed.)

photo

Cafe Nomad 蒐集 1,700 間咖啡廳的理由

我是一個很常去咖啡廳用筆電的軟體工程師。

每次在巷子內找到新的獨立小店時,我心中都會浮現3個念頭:

1. 又是一間精緻用心的獨立咖啡廳。這種店台北應該有幾百家,如果每間都去過,一定很有成就感

2. 這間店的位置真是太低調了,就算開幕滿一年,恐怕連附近住戶都不知道它的存在

3. 根據以上兩點,好像可以推論:巷子內的這些咖啡廳,普遍被埋沒了

2016年11月,趁著離職之後時間多,我決定驗證一下這些直覺。

我把過去幾年去過的20多間咖啡廳資料整理出來,根據自己最在乎的7個指標做了評分,接著上網再找出台北60間咖啡廳的店名,作成 Google 試算表,等待網友幫忙評分。

把這個試算表貼到批踢踢的軟體工程師看板之後:

[討論] 台北適合工作的咖啡廳

反應很不錯,隔天下午時,大部份咖啡廳都得到網友評分了,資料也被新增到100多間。

然後,我寫了程式把試算表的內容轉成網頁版的表格,接著訂購了一個網址:cafenomad.tw

稍微做了SEO與SMO之後,我把這張表格分享到我的一個粉絲專頁:

https://www.facebook.com/permalink.php?story_fbid=982000048579054&id=650279948417734

這則貼文隔天被分享了1,200次,再被轉分享之後,最後共計被分享了30,000次,當天有90,000人來看 Cafe Nomad:

1

這些流量也帶來更多人幫忙整理咖啡廳資料,最後台北收錄了200多間,其他地區也共計有200多間。

網友們的熱情參與,幾乎證明了巷子內的這些咖啡廳果然被埋沒了。

換句話說,台灣獨立咖啡廳產業的整體產值被低估了。

那麼,該如何提昇台灣獨立咖啡廳產業的整體產值呢?

我想到三個辦法:

1. 針對原本就會去獨立咖啡廳的族群:鼓勵他們更頻繁地消費
2. 針對只去連鎖咖啡廳的族群:邀請他們試試看獨立咖啡廳
3. 針對近年來與日俱增的國際觀光客:鼓勵他們去咖啡廳喝一杯

針對原本就會去獨立咖啡廳的族群

這是其中最簡單的方向。只要把這份清單給他們看就行了。

然後不斷開發更好用的找店功能,然後定期告訴他們有新功能就可以了。

這個族群也容易觸及:只要去批踢踢、Facebook社團貼文就可以了,大概像這樣:

[閒聊] 我做了一個蒐集956間咖啡廳資料的網站

[閒聊] 我做了一張台中咖啡廳地圖

[閒聊] 一口氣逛高雄全部咖啡廳的FB粉專的方法

[閒聊] 台南咖啡廳地圖:150間店 + 1,000張照片

https://www.facebook.com/groups/1507207486163325/permalink/1793163560901048/

這些貼文的反應都很不錯,也讓更多人知道巷弄小店要去哪裡找。

針對只去連鎖咖啡廳的族群

這是一個十分困難的方向,但還是有出力空間。

時不時會有咖啡產業的新聞出現,這種時候直接去人多的地方,然後跟大家談談自己對獨立咖啡廳的觀察跟心得就可以了。例如八卦版就很適合去po文:

Re: [問卦] 為什麼台北都很少小資本額的咖啡館?

把握住這種機會的話,可以吸引到至少50,000人開始想試試巷弄小店。

2

近年來與日俱增的國際觀光客

這是最困難的方向,光是要觸及這些旅客就很難,但還是有出力空間。

我把網站介面翻譯成了英文版:

https://cafenomad.tw/en

然後貼到幾個在台灣的英文社群網站:

https://www.reddit.com/r/taiwan/comments/5uw792/cafe_nomad_700_best_cafes_to_work_in_taiwan/

https://www.facebook.com/groups/357339197762790/permalink/723135624516477/

接著找 Cafe Nomad 社群的人幫忙翻譯成日文韓文版

https://cafenomad.tw/ja/

https://cafenomad.tw/ko/

然後再貼到相關的台日交流社團、台韓交流社團:

https://www.facebook.com/groups/japantaiwan/permalink/1392935974078433/

https://www.facebook.com/groups/partyintaiwan2/permalink/1453874974920063/

反應普遍很正面,雖然比較間接,但還是有點效果。

結語

網站上線5個月之後,我開始會在FB收到店家這樣的訊息:

3

Cafe Nomad 現在已經蒐集超過1,700間店、超過3,500筆評分、500則留言。每天都有超過1,000人主動上站尋找他下一間要去的巷弄小店。

最近替 Cafe Nomad 新增了店家贊助的功能,也找到了幾間贊助店家,所以我也得到了一些報酬。

在過去三年,我去這種巷弄小店消費了幾百次。整體說起來,在台灣,你很難找到一間會讓你失望的獨立咖啡廳。

花了很多時間做這網站,其實我也不太確定自己在幹嘛,但因為是滿有趣的事情,所以就算完全行不通,好像也沒關係。

我唯一確定的是,台灣這些巷弄小店,確實普遍被埋沒了。


附註:

店家贊助」功能是我替 Cafe Nomad 最新加上去的廣告功能。

它會將咖啡廳的名稱與照片獨立顯示出來,並且在清單與地圖內特別呈現。

歡迎有興趣的店家點這裡看看這個贊助功能的細節

(Photo via unsplash.com, licensed under Creative Commons Zero)

我現在就想用你的產品,就算破破的也請馬上給我用

「當你有一個創業點子的時候,問問自己:有誰現在就想要這個產品?有誰超級渴望這產品,即便是小團隊做出來的破爛版本、即便這團隊根本沒人聽過,他們也照樣想馬上使用?如果你無法回答這個問題,那這個創業點子恐怕不太妙。」── Paul Graham

每次聽到有人正打算要創業、分享點子,我腦中都會浮現上面那個 Paul Graham 提到的問題。

不妨用更尖銳的方式描述這個問題:「這東西有人要用嗎?誰?你認識或見過任何一位這樣的人嗎?你說得出那人的名字嗎?現在打電話給他介紹這產品,他會說:『我現在就想用你的產品,就算破破的也請馬上給我用』?」

這個問題乍看之下沒什麼,實際投身新創產業之後發現,它會在很早期就給你迎頭痛擊。

如果無法回答這問題,那把產品推出之後,要是沒什麼人用,就會心想:是我功能不夠豐富、是我介面不夠漂亮,那再改一下。

改完一下再一下,還是沒人用的話就再改一下。

再不然就是會心想:都是我廣告下得不夠多。

於是付點錢給Facebook、付點錢給Google,還是沒人用的話就再燒多點廣告錢。

如果你自己都不知道這產品是誰會馬上想用,那就幾乎一定會陷入上述的挫折感循環,永無止盡投入的陷阱。

毫無正向回饋的不斷付出,會讓人感到焦慮、靈感枯竭,覺得自己像是跑步機上面的老鼠,沒有目的地疲憊奔跑著,卻也真的只是在跑而已。

在搞定了上述第一個問題之後,還可以參考 Paul 提供的另一個建議:

「相較於讓一堆人普通滿意,還不如讓少數人非常滿意比較好。」── Paul Buchheit & Paul Graham

然後你就會針對很明確的一小群人,非常專注地打造產品給他們。

這些建議之所以那麼多人沒注意到,是因為它們非常違反直覺。

它們乍看之下似乎是在劃地自限、從一開始就把東西做很小、從一開始就把市場弄很小。

實際上並非如此,它們只是一種讓自己能在一開始就保持專注的方法。可以接著把產品越做越大。

「擴展市場大小比提升用戶滿意度簡單。更重要的恐怕是,你比較難欺騙自己。如果你自認產品品質已經85分了,你怎知道它不是70分?說不定只有10分?但是你很容易就知道產品有多少用戶。」── Paul Graham

產品即使一開始只讓一小群人愛不釋手,但使命仍然可以定的很巨大。努力往那裡走就是了。

就算是一邊做一邊確立使命感,都好過於一開始就好高騖遠。

而且這種作法會大幅提昇產品上線速度、新功能上線速度。

愛情裡有一個關於緣份的描述:「世界上有一個人,每天每天都在想,要是有一天,能有像你這樣的人出現,那該有多好。」

不妨直接拿來當作創業的比喻:「世界上有一些人,每天每天都在想,要是有一天,能有像你做的這樣的產品出現,那該有多好。」

她當然知道你不完美,她當然知道你一定有缺點,但是她見到你的瞬間就已經決定包容你的一切。

有些人正在等你介紹你的產品,然後他們會對你說:我現在就想用你的產品,就算破破的也請馬上給我用。

創業:退後100步的做法

會選擇創業的人,大概都是對某種現況感到不滿,想要改變那現況,所以才創業。

「我要改變世界!當一個有影響力的人!在宇宙間留下點什麼!」他們會這麼說。

有趣的是,要改變現況、影響別人,事實上不需要下定決心創業、不需要負債百萬、不需要籌備一年半載。

你今天下午就可以穿上鞋子出門,然後改變現況、影響別人。

這兩者的差別在於:你多大程度上改變了現況?你影響了多少人?

讓我們隨便舉個小明想要創業的例子。

小明最近發現海灘上常常有很多垃圾,於是他想到了一個網路創業的點子:成立一個「能與網友一起淨灘的社群網站」。

它是一個社交網站,你能邀請感興趣的網友週末一起去淨攤,在做環保的同時,還能交到知心朋友、找到男女朋友。

商業模式呢?嗯,小明心想,創業做大之後就是大,隨便弄都能賺到錢。把淨攤蒐集到的垃圾統一分類,做資源回收賣掉,數量一大就發財了。做環保的同時還能交友,多棒!不然跟會員收取會員月租費或是年費吧!人夠多也是發財了。

小明的社群網站上線之後大概沒什麼人註冊,創業幾個月都沒有網友相約去淨攤,更不用說向使用者收費了。

小明創業幾個月之後發現這點子不可行,於是把網站收掉了。

小明沒想到退後100步的做法。

同樣喜歡淨灘的小華不同。

小華對於海灘污染的問題感到非常痛苦,他第一次看到骯髒的海灘就決心要改變了。

小華每個週末都會帶著垃圾袋,開車幾小時到海邊去撿垃圾。撿了幾年之後,他會邀請身邊的朋友一起去,大部份的人會拒絕,但有些人會答應。

小華可能也有過「能與網友一起淨灘的社群網站」這樣的點子,但他是如此渴望改變海灘,他根本等不到網站寫出來,他今天下午就想動手改變海灘的現況。

「做一陣子再邀請朋友一起吧」小華這樣心想。

「做一陣子再去發傳單,找路人一起吧」小華這樣心想。

「做一陣子再上網號召,找網友一起吧」小華這樣心想。

小華選擇了退後100步的做法,然後他知道往前一步的做法,還有再往前一步的做法。

上面這個「淨攤交友兼賣資收與賺會員費的社群網站」是我隨便想的例子,看似很扯,但是在網路創業圈,到處都是類似這樣的例子。

它們都有一個共同點:上線之後沒什麼人要用。

這時候該怎麼辦呢?可以用退後10步的做法再試試嗎?還是失敗怎麼辦?退後100步如何?

事實上,創業圈的人們大都很聰明、很有想法與創意,要想出退後1步、10步、100步的做法並不難。

真正困難的地方在於:你願意嗎?

退後10步的做法會變成要幹一堆麻煩事,創業者要捲起袖子做一堆很像在打工的事,不規模化也賺不了多少錢。

退後100步的做法會變得很像在做慈善,很像在做社會運動,根本賺不到錢,完全就是勞心勞力在對世界、對別人付出,根本沒賺頭,而且一點都不酷。但是對現況、對別人,都有直接影響力。

不是說想改變現況嗎?為什麼規模小一點就不做了呢?為什麼無法立刻賺到錢就不做了呢?

然後你才發現,他們沒那麼想改變世界。他們的改變世界有先決條件:改變世界的同時,我要能發財才行。

改變世界的同時,還要看起來夠酷才行。

賺不了錢我就不改變世界了。不夠酷我就不改變世界了。

但是在創業的歷史上,從一開始就有利可圖、從一開始就很酷的例子根本少之又少。

幾乎總是要在挫折之後,退後個幾步再試才行。或是從一開始就從退幾步的地方開始做起。

卻也只有真正對改變某種現況有使命感的人才願意這麼做。

觀察一下那些組織社會運動的人,他們不會說「我要上網號召萬人上街!但沒有萬人答應的話,我就不上街了」。

他們對現況是如此憤怒,以致於無論有沒有人理他,他都要出發去高喊理念,他都要冒著風險去衝撞、去挑戰現況。

也只有當你這麼做的時候,才真的可能發揮影響力。

你正在創業嗎?你想改變什麼現況呢?你對和你一樣對現況不滿的潛在用戶發表什麼理念?你要推出什麼產品或是服務來幫助他們?

你對他們做出多少承諾?

為了解決問題,你願意退後10步嗎?

為了解決問題,你願意退後100步嗎?

Laravel-–-a-beautiful-PHP-framework

Laravel 的 routing 是如何支援開發者使用 method injection?

前陣子參加線上讀書會,聊到 Laravel 5 的 controller 允許開發者使用 method injection

好奇是如何做到的?

今天 trace 了一下,發現此功能寫在 Illuminate\Routing\RouteDependencyResolverTrait 裡面

然後 Illuminate\Routing\ControllerDispatcher 使用了此 trait

並且在此處呼叫

這就是 Laravel 5 controller 提供 method injection 的地方。

順帶一提,直接用 closure 的 routing 寫法的話,一樣支援 method injection

它在 Illuminate\Routing\Route 使用了此 trait

並且在此處呼叫

Laravel-–-a-beautiful-PHP-framework

讀 source code 研究 Laravel IoC Container 實作的一點心得

前陣子在SO回答了一個問題

http://stackoverflow.com/questions/27341595/contracts-in-laravel-5/29812568

這問題不難,官方文件就有寫。

過幾天,下面的comments接著有網友 amosmos 提問 IoC Container 實作細節的問題。

他的問題考倒我了,完全無法回答。

於是花了幾天讀原始碼,最後終於搞懂了。

跟大家分享一下這個心得,也順便分享下我在過程中,讀原始碼的整套思路。

正文開始

簡單來說,網友 amosmos 的問題如下:

在 Illuminate/Foundation/Application.php 檔,會在 function registerCoreContainerAliases 看到這些:

讓我們先稱呼’app’、’auth’這些字串為 IoC Container 內的 「key」 好了。

乍看之下,大部份的 service 都在這時候被綁定到 IoC Container內,對應到某個 key。

看起來非常合理,也很漂亮,對吧?Laravel會用到的service都在這時候統一登記好、綁進這IoC Container。

問題來了,在 config/app.php 內,有這個陣列:

你如果熟悉 Service Provider (下文以SP代稱) 的話,就會知道每個 SP 內必定有 function register,負責綁定service到container。

以CacheServiceProvider來說,會出現這幾行:

問題來了,在上面的 Application.php,已經有這個了:

所以這CacheManager,到底是何時被綁定到 ‘cache’ 這個key上的?

是Application在建構式呼叫registerCoreContainerAliases時綁定的嗎?

還是在某處讀取 config/app.php 的providers時綁定的?

這邊出現了我們的問題一:

Q1: 難道重複綁定了兩次嗎?CacheManager被初始化了兩次?所以是 Laravel 原始碼的瑕疵?

讓我們做個實驗吧,把Application.php裡面這行comment掉:

接著在cmd輸入

php artisan tinker

很好,跑起來了,至少laravel沒有炸掉。

接著試試cache功能是否還活著?

看起來沒問題!難道真的是Laravel的瑕疵?

接著把 Application.php恢復原狀,然後把CacheServiceProvider的綁定給comment掉:

接著在cmd輸入

php artisan tinker

[ReflectionException]
Class cache does not exist

Laravel炸掉了!根本不能跑!

所以不算是重複綁兩次?

那就出現了我們的問題二:

Q2: 為什麼 Application.php 內的key給comment掉,功能還是正常運作?

反覆翻閱一下 Application.php、Illuminate\Container\Container.php,會發現 function registerCoreContainerAliases 似乎將整串陣列存在container的成員變數$aliases上?

接著尋找讀取config\app.php的地方,會找到 Illuminate\Foundation\Bootstrap\LoadConfiguration.php這檔案,然後發現Illuminate\Foundation\Bootstrap\RegisterProviders.php這檔案,翻閱Application.php和 Illuminate\Foundation\ProviderRepository.php之後,會發現config\app.php內的providers似乎會存在container的成員變數$bindings上?

有一個很簡單的方法去驗證這件事情。

將 Illuminate\Container\Container.php 的 $aliases 與 $bindings 成員變數改成 public,接著 php artisan tinker

會發現的確是這樣沒錯!

事到如今,我們會發現Q1的問題解決了:Application.php是存在aliases內,而config\app.php是存在bindings內。並沒有重複綁定兩次的問題!而是container實作細節沒搞清楚的問題!

所以真正的問題應該是這個:

Q3: IoC Container內的$aliases跟$bindings分別是什麼?兩者如何互動?

要解決這個問題,必須去讀container最核心的function make部份程式碼。

看到這邊,我們幾乎可以確定$aliases跟$bindings的關係為何了:

我們在使用 App::make($abstract) 時,輸入的abstract,實際上主要是去bindings找對應並叫出來。

而aliases只是紀錄abstract的其他別名而已,舉例如下:

還可以再做進一步的測試,將Container的function alias改成public,然後做以下測試:

所以正式回答Q3的問題:

Service Provider的function register內會去登記到IoC Container的abstract,實際上是存在$bindings或$instances陣列。

而App::make($abstract)實際上也是從這兩個陣列找東西出來用。

$aliases陣列只是存abstract的其他別名,讓 Binding Interfaces To Implementations 成為可能,也允許直接打類別全名進去。

結論

讀Laravel原始碼有幾個小技巧

1. 多用php artisan tinker去跟laravel互動

2. 把一些source code的變數、函數改成public,在tinker硬倒出來觀察

3. 有些東西很難在一天內搞清楚,睡個覺醒來再看通常很有幫助

(完)

quality

開發網站時,寫測試的3個簡單方法

許多想要學寫測試的朋友,常常不知道怎麼開始。

其實寫測試比大多數人以為的還要簡單。

就算是小型的網站、簡單的購物網站,也可以利用軟體測試加強軟體品質。

今天跟大家分享3個簡單的導入測試開發方法。

(本文使用 Laravel + PHPUnit 示範,但相關概念通用在任何語言/工具上。)

第一招、controller轉移到entity

開發網站時,大部份商業邏輯都在對entity(例:產品、訂單、使用者…etc)操作。

相關的code通常會散在controller裡面。

舉例來說,假設我們在寫一個購物網站,那麼把產品加進訂單、更新訂單金額的code會像這樣:

寫測試的目的是為了增加開發者的信心。

像上面這樣的code,有些人會覺得打開瀏覽器按個幾次,看看最後訂單金額是否正確就好。

反覆按個幾次,就算不寫測試,也對程式品質很有把握、相信它沒有bug。

但如果程式再複雜一點呢?例如增加不同運費的邏輯:

有些人依然覺得這不複雜,在瀏覽器上多操作幾次,然後看看金額、運費是否都正確即可。

但如果你像我一樣,採取比較嚴謹的立場,預設自己寫的所有code都有bug,那麼可以先把code從controller搬到entity:

然後可以這樣寫測試:

像這樣的測試寫好之後,就不用每次改動一點商業邏輯,就打開瀏覽器重複做一堆人工測試了。

如果運費的算法更複雜,根據訂單金額而有4、5種運費金額的話,

光是要測試運費邏輯,就要人工去操作下單流程好幾次。

這種時候,寫測試方便多了。

這種開發流程,我稱為「controller轉移到entity」。

一開始邏輯單純的時候,直接寫在controller即可。

複雜起來後,再把code轉移進entity,然後補寫測試即可。

第二招、轉移到service

接續上面的例子,來看看更複雜的例子。

現在產品賣掉的時候,產品本身的庫存數量要減一。

以原本的寫法來說,需要在entity加上這一行:

然後在測試內多加幾行:

看起來沒多大問題。邏輯放在Order內還算合理,該測的也都測到了。

但如果邏輯再複雜一點呢?

假設有人氣指數的功能,在賣出去的同時,商品的人氣指數要加上10分:

這行code還是放在Order類別內嗎?

為什麼一定是$order->addProduct($product),而不能是$product->addToOrder($order)呢?

隨著Product相關邏輯變多,商品加入訂單的功能不再完全像是訂單的事,也很像是Product的事。

像這種不知道要放在Order內還是Product內的時候,可以獨立出來成Service類別:

測試的時候,改寫成這樣即可:

跟第一招一樣,可以在程式變大之後、有必要的時候才獨立成service類別並補寫測試。

轉移的成本是很低的,幾乎只是把code從這邊剪下貼上到那邊而已。

第三招、轉移到專門的POPO

POPO(Plain Old PHP Object)是指單純的全手寫類別,不去繼承Eloquent之類的華麗類別。

雖然是最基本的OOP用法,卻常常被人們所忽略。

現在假設訂單進一步變複雜,運費的邏輯會根據消費者的所在地區而有不同:

再加上金額的變化,光是運費本身的計算就很複雜、需要寫很多測試才安心。

與其在entity或service內計算,不如弄一台運費計算機出來,會更分工明確:

接著就能替計算機寫許多測試,測到安心為止:

本來的service就可以改寫成,吃計算機當作參數:

POPO常常被人們忘記,因為太習慣把code全都寫進entity或是controller裡面。

其實,在程式複雜起來之後,最好把意義上獨立的部份各自獨立成類別。

然後分別替這些POPO寫各式各樣的測試,就能大幅增加軟體的品質與穩定性了。

結論

寫測試的目的純粹是為了增加安全感,讓工程師晚上安心入睡而已。

不需要撲天蓋地般地狂寫測試,適量的測試就非常足夠。

以中大型專案來說,寫測試可以幫助省下「非常大量的時間」。

實務上,在一開始商業邏輯不多的時候,寫在controller通常沒什麼問題,把幾個變數存進資料庫而已,不太需要寫測試。

等到邏輯慢慢變複雜,發現不寫測試會不敢上線的時候,再拉出來並且補寫測試即可。

希望你之後的專案開發,可以試試看寫測試帶來的好處與美妙!


歡迎訂閱轉個彎日誌的粉絲專頁,我很樂意和你分享各種心得。

(Photo via Elizabeth Hahn, CC licensed.)

by 阿川先生