由 howtomakeaturn 發表的所有文章

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

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

baby

不要浪費開發時間:給新創公司的6個軟體開發建議

新創公司因為產品定位還不明確,常常花一堆時間做好功能,卻發現沒人要用,浪費了寶貴時間。

我在2014年底加入一元翻譯,一個已有穩定客源與翻譯師的翻譯團隊,負責開發系統來協助公司處理與日俱增的文件。

原以為這套系統的定位明確、連使用者都已經有了,因此容易開發,結果初版的系統開發還是犯了一些錯誤。

踩雷幾次之後,我們慢慢整理出一些比較有效的開發方法。

這些方法幫我們省下很多開發時間,今天跟大家分享一下!

一、別在一開始就想要流程全自動

常常聽到正要創業的團隊擔心「要是我們太受歡迎怎麼辦?」、「要是我們太紅怎麼辦?」

直覺的想法會是「當然要先做好準備再上線!」、「把系統做到全自動,讓工作人員不需要介入,整個流程就能順利跑完!」

我們也犯過類似錯誤:既然都已經有穩定客源了,請他們改用更方便的全自動系統應該不難吧?

結果系統上線之後,舊有客戶大多繼續用 Email 和電話與我們來往。即使我們提供額外折扣,他們一時之間還是不習慣新的下單方法。

所以我們後來設計新功能、新產品的時候,便不再一開始就把所有流程自動化。

客戶習慣的部份,不要輕易做大幅度調整。先針對很花時間、重複性很高的部份做自動化,比較有效率。

其餘的部份,快速寫出一個「半自動」的系統,然後直接上線。

收集Feedback系統?用個Google表單嵌入在網頁裡面就很像了。

自動寄出電子發票系統?做個「寄出發票」按鈕,給客服人員自己找時間去按就好了。

合作夥伴註冊系統?用個Google表單嵌入蒐集資料,然後自己用 Email 一個一個聯絡就好了。

商品具有多種狀態的全自動物流系統?其實在資料庫用不同整數代表各種狀態,給物流人員手動調整所有商品的所有狀態就夠用了。

做出幾個功能按鈕,讓負責人員自己判斷什麼時候去按那些按鈕就好了。

情境判斷與自動化的程式碼都先別寫了。需求量大到負責人員忙不過來再寫就可以了。

二、信任你的內部人員

不信任的成本是很高的。

公司常常需要針對管理員、工程師、合作廠商、實習生等等不同角色,開發權限系統。

真的要針對每種角色,在後台管理面板寫一套大企業等級、能夠做完整內控的權限管理系統嗎?

底層不作嚴格檢查、只針對各角色顯示不同資訊,真的不夠用嗎?

相關的內部人員真的會惡意到去測試系統漏洞、亂改不屬於他們的資料嗎?

真的發生這種事,在算帳時會發現不了嗎?被影響到的人不會來反應嗎?

我們也曾因為預計之後會有獨立的PM權限,於是在目前的ADMIN權限之外多寫了PM權限。

結果因此浪費很多開發時間,而且根本沒那麼快招募專門的PM人員。

完整的權限系統會需要在controller或是更底層的layer寫一堆檢查的程式碼。

更糟糕的是,各個角色的職責跟功能也變動得很快,複雜的權限系統會讓各種功能改起來更慢。

先實作一套簡單陽春的後台權限檢查,通常會夠用好一陣子。

 三、把功能拆分成階段上線

這是所有建議裡面,我們付出最多代價,才學到的教訓。

不要在一開始就把功能做到豐富完整。

大部份的功能,都可以在討論過後,拆分成階段上線。

討論出功能最基本的長相(Phase 1),試著在幾天到幾週內開發然後上線。

Phase 1上線後會碰到各式各樣的問題,像是使用者不想用、不會用、用了卻不爽…等等。

不如讓 phase 1 先上線,再根據結果,思考phase 2要做哪些事,或是乾脆擱置這個功能不做下去。

這種作法能讓服務快速上線、團隊的下一步明確、省下不必要的開發時間。

以我們一元翻譯為例,公司的營運有兩段流程:客戶送文件給我們、我們送文件給翻譯師。

這兩段流程原本都是在Email上進行,也就是很傳統的作法。

為了讓這個流程自動化,需要開發一個類似購物網站的電子商務系統,讓客戶透過網站下單、翻譯師透過網站收到文件。

直覺上會從客戶下單介面開始,接著做內部管理面板、翻譯師工作面板,把整個系統寫完。

但如果真的一口氣把系統寫完,風險會非常巨大。

要是客戶都拒絕透過網站下單、堅持繼續用Email寄檔案給我們怎麼辦?

要是翻譯師不喜歡用工作面板接案,習慣用Email跟我們互動怎麼辦?

那原本預期的流程就完全行不通了。

為了避免這種「寫出一整套solution,上線後卻行不通」的慘劇,我們  phase 1 只開發「送文件給翻譯師」系統。

也就是客戶繼續用 Email 給我們檔案,我們的PM手動把文件上傳到系統,接著系統通知翻譯師,翻譯師透過網站接案與交稿,PM最後再把成品用Email寄給客戶。

對客戶來說,流程跟原本一模一樣。

upload

(圖一、PM用這個面板把檔案丟進系統)

projects

(圖二、PM跟翻譯師在這個面板瀏覽文件、翻譯文件)

Phase 1 上線後,我們蒐集翻譯師的意見、不斷改善系統、改到翻譯師覺得系統好用為止。

然後才去開發「客戶送文件給我們」的系統。

但因為怕客戶「堅持只用Email與我們互動」、「操作網站會很沒安全感」,我們的 phase 2 從提供一個確認報價的連結開始:

email

(圖三、收件後繼續由PM寄Email給對方。但要求他至少打開一個網頁。)

confirm

(圖四、報價資訊只寫在網頁內,不寫在Email內,鼓勵客戶去按那個大按鈕。如果客戶拒絕去按,而寧願寫Email回信,那這個 phase 2 就算是失敗。)

結果大部份的客戶都願意去按「同意報價」按鈕!只有少數客戶選擇用Email回信。

確定客戶們至少「願意在Email以外與我們互動」,我們才動手把圖一的PM下單面板做成公開版本的 phase 3,讓客戶能夠自行下單。

如果沒有分階段開發,這個系統可能會開發非常久才上線。

四、盡量去追蹤網站的使用情況

網站到底哪些功能常被使用?哪些地方客戶逛了但是沒去用?哪些地方客戶根本逛都不逛?

除非你站在客戶旁邊看他操作系統,否則很難得到答案。

網站的下一步要怎麼改善,團隊裡的每個人都有不同想法,這種時候,最好能用數據來說話。

因此,應該要盡可能地去紀錄每個按鈕、每個連結、每個頁面的使用數據。

安裝Google Analytics是基本的。

除此之外,記得替幾個你覺得重要的按鈕加上Google Analytics的Event Tracking功能;

有在網路上公開的文章、貼文,可以用Bitly之類的工具紀錄網址使用情況。

再不行的話,就在系統本身建幾張資料表,直接用程式碼去紀錄某些功能的使用數據吧。

五、工程師做做看客服,做做看業務

這點跟開發沒有直接相關,但還是會影響開發速度。

負責客服的同事可能多次向工程師反應過某個頁面很難懂、按鈕很難用。

工程師常常會去忽略這些反應,心想「那個頁面最好是有這麼複雜」、「你跟那些客戶多解釋一下就好了」。

然後就會導致每個人對於接下來什麼事最重要有不同看法。

這種時候,如果工程師花點時間去做客服,通常會有很大幫助。

他可能會驚覺「居然這麼多客戶反應同樣問題」,於是充滿鬥志的把功能改好。

也可能會發現「同事沒講清楚,但其實加點字、改改按鈕顏色就解決了」。

同樣的,如果工程師花一些時間做業務,可能會突然理解為何負責業務的同事會一直要求某個功能。

工程師偶爾換一下角色,做點其他事情,不但能讓團隊溝通更有效率,開發起來也會更有士氣。

六、先讓介面簡單易用

我們在設計第一版網站的時候,想把網頁弄得漂漂亮亮、希望它很有質感。

後來發現如果它的功能本身不受歡迎,那再漂亮也沒用。

在不確定會多受歡迎的情況下,不如先做到使用者知道系統怎麼用就好。

所以JavaScript預設的alert、confirm、prompt函數,其實依然很好用。

一元翻譯來說,我們把報價頁面做好之後,客戶卻多次反應「不知道怎麼確認報價」。

因為不想花時間去大幅調整設計,我們於是直接加上又大又紅的提示文字,再搭配一個大大的綠色按鈕:

red

(又大又紅的提示文字)

green

(48px 超巨大綠色確認按鈕)

雖然不太好看,但這個介面開發時間很短,而且上線之後,再也沒有客戶抱怨找不到確認按鈕了。


以上六點就是我們花了許多開發時間後,整理出來的一些建議。

說穿了其實就是:盡可能地將開發時間花在確定有價值的事情上面。

但也不需要太怕犯錯,初次開發產品多少都會遇到類似問題。

最重要的是有明確方向、小步驟地實驗與驗證,同時又保持足夠彈性來根據實際使用狀況做調整。

如果您有任何想法或是其他開發建議和大家分享,歡迎在下方留言!

 

工商服務時間:

本篇文章的內容,大多是來自我在一元翻譯開發系統的心得。

需要專業翻譯服務的朋友,歡迎來一元翻譯的官網逛一逛!

文中提到的客戶下單系統,可以在這裡看到它的真面目!

也歡迎到一元快報看看我們翻譯的各類優質文章!

(Photo via Sano Rin, CC licensed.)

learn

給OOP初學者的建議:先搞懂「資料跟行為在一起」就好,其它的慢慢來

初學者接觸OOP,幾乎都會有以下疑惑:

我到底為什麼要學OOP?OOP解決了什麼問題?書上這些範例就算不用OOP也寫得出來吧?

然後覺得「繼承」、「多型」、「介面」、「抽象類別」等等的名詞很難,覺得OOP很難。

其實這些名詞雖然重要,但對新手來說,本來就很難在一開始就搞懂。

建議先搞懂「資料跟行為在一起」是什麼,以及它的好處在哪,就可以了,其它的慢慢來。

什麼叫做「資料跟行為在一起」?

假設我們在開發一個「中英文互助學習網」,鼓勵中文人士與英語人士登入討論。

這個系統的貼文、留言功能會顯示「發文日期」。

發文日期要根據使用者的註冊身份(台灣人、英語人士)顯示不同格式(台灣格式、西方格式)。

下面就以這個日期格式的功能舉例說明「資料跟行為在一起」是什麼意思。

作法一:直接硬寫(不OOP、資料跟行為混在一起)

初學者通常會用最簡單、也最直覺的作法,直接硬寫出來,像這樣:

這種寫法的資料(日期)跟行為(轉換成各種格式)混在一起。

它的優點是寫起來很簡單,缺點則有兩個:

* 日期格式的邏輯會重複出現在很多地方,整段code會到處重複出現
* 整段code大概會塞在<div>或是<span>的裡面,導致它跟HTML混在一起,很亂

作法二:自訂函數(不OOP、資料跟行為沒混在一起)

為了解決作法一遇到的問題,聰明的初學者很快就想到可以用「自訂函數」!就像這樣:

這種寫法將行為(轉換成各種格式)用自訂函數給獨立出來,也大幅改善了作法一遇到的問題。

對小型的網頁程式來說,這招非常好用,不但開發快速、簡單,還漂亮地將資料跟行為拆開。

但是程式規模變大之後,為了將各種行為拆出來,會寫出很多自訂函數,類似這樣:

於是又衍生出三個問題:

1. 像localFormat、englishFormat這樣的函數名稱意義模糊,看不出是處理日期、人名,還是什麼東西的格式

2. 這些自訂函數各有不同的行為,全部放在一起顯得很亂,應該要想辦法分類、整理這些函數

3. 像localFormat、englishFormat這樣的函數,只吃特定格式的參數,最好能跟某種資料的形式綁在一起,以後要改程式時,能讓相關的資料跟行為一起被看到

問題1很好解決,只要替函數名稱加前綴字變成dateLocalFormat、dateEnglishFormat就行了。

問題2也很好解決,只要多開幾個檔案,把相關的函數放進同一個檔案就行了。

問題3就很棘手,資料跟行為拆開之後,如何在概念上又找方法整理在一起?

作法三:使用class(OOP、資料跟行為在一起)

正是這些處理資料、整理行為的問題,導致了OOP的誕生:

OOP的寫法,一次解決了前述三個問題:

問題1 => 現在從類別名稱就可以知道底下方法的意義了

問題2 => 現在相關的函數都整理進同一個類別底下成為方法了

問題3 => 現在資料的形式都統一在constructor處理一次,之後不管新增多少方法都不用處理資料了

這就是所謂的「資料跟行為在一起」,也正是OOP的核心概念。

利用這種方式整理程式碼、寫出一個又一個的類別,可以大幅提昇程式碼的品質。

結論

上述的作法一跟作法二並沒那麼糟糕,但確實會帶來一些問題。

對於小型的網頁程式來說,可能還算夠用。

但是隨著程式規模變大,如果將概念上相關的資料跟行為整理在一起,會很有幫助。

實務上也可以先從作法二開始寫起,直到發現某些資料跟行為關係密切,再拉出來整理成類別即可。

至於很多OOP教學會提到的「繼承」、「多型」、「介面」、「抽象類別」等等名詞,一時搞不懂沒有關係,你可能實務上也暫時用不到。之後找時間慢慢搞懂它們的用途就好。

光是知道「將資料跟行為放在一起」的技巧,就能夠開始寫OOP程式碼了。

(註:本篇文章的程式碼純屬教學用途。實務上PHP已經有DateTime類別可以使用,或是用更漂亮的Carbon類別。)

Q&A

Q1:我常常設計一些類別,只有資料沒有行為,聽起來OK嗎?

不OK,這很不OOP,而且沒意義。

乾脆直接用關聯式陣列去表示那些資料就好。

Q2:我常常設計一些類別,只有行為沒有資料,聽起來OK嗎?

這個要看情況,不一定。

但唯一可以確定的是,這種作法很不OOP。

因為OOP的核心是「資料跟行為在一起」。

這也是為什麼你會看到有人明明寫了類別、用了物件,別人卻說「這不夠OOP」。

然後你又會看到像JavaScript這樣連「類別」關鍵字都沒有(ES5以前),卻能夠寫出很OOP程式碼的關係。

判定的標準都是一樣的,而且也就只有這麼一個標準:資料跟行為有沒有在一起。

Q3:一個類別包含的概念是越大越好,還是越小越好?

不一定。不過我們從作法一到作法三的過程,有一個明確目的:希望讓程式碼更好懂。

如果聲稱一個類別包含的概念很大(例:設計LanguageHelpWebsite類別,用來代表「中英文互助學習網」需要的所有功能),那會把幾乎整個網站的所有行為跟資料都放進去,成為所謂的God object。它可沒有讓程式碼更好懂。

相反地,如果聲稱一個類別包含的概念很小(例:分別設計LocalDate、EnglishDate類別),雖然意義可能更精準了,但用一整個Date類別的概念去思考,程式碼會更容易理解,也就是所謂的內聚性(Cohesion)更高。

所以要替OOP就是「資料跟行為在一起」加個但書:

要以方便理解程式為前提,將資料和行為放在一起。

(完)

(Photo via brando.n, CC licensed.)

jason_fried

[翻譯] Basecamp共同創辦人:不必擇你所愛,也不必愛你所選

如果你常參加創業聚會,或是常去聽那種企業家的勵志演講,你會很常聽到這種論調:你必須擇你所愛,愛你所選!如果你沒有的話,那你乾脆別出來混了。最著名的例子是Steve Jobs 2005年在Standford畢業典禮上的演講:「想要有出色成就,唯一方法就是做你熱愛的事。如果你還沒找到,那就繼續找下去,不要停下來。」

我完全不信這套。

熱愛自己的工作當然沒有錯。但我不覺得這是創業或實現抱負的先決條件,也跟什麼出色成就無關。說真的,事業有成的人吹噓自己對工作的熱愛實在很虛偽。就跟富有的人說自己不在乎財富一樣虛偽。人們常常會浪漫化自己的動機跟過去。他們會把自己現在所在乎的東西想得很重要,而忘記他們一開始在乎哪些東西。人類天性如此,很容易就會這樣。

根據我的觀察,許多出色企業跟重要創新,根本是源自於挫折,甚至是厭惡。Uber的共同創辦人 Travis Kalanick 和 Garrett Camp哪是因為熱愛運輸與物流才創業的。他們創業是因為舊金山很難叫計程車,搞得他們一肚子火。Kalanick現在大概滿喜歡經營Uber的,但他之前真的超痛恨叫不到車回家。巴黎一個腦力激盪的夜晚,讓那份挫折轉變為一棵幼苗,孕育出一間市值數十億美金的公司。

我常常跟其他企業家聊天,很多人開公司都是基於類似的理由:他們想要的東西市面上沒有,或是想用更好的方法去改變舊的做事方式。至於是否熱愛則未必重要。但是對於現有選項的厭惡、對於事物運作方法的強烈意見則影響極大。能否成功也和它比較相關。

我的職業生涯也是這樣。大概在90年代的時候,我想找一個能幫我紀錄音樂播放清單的小工具,但市面上的軟體都很肥大,而且過度複雜。這兩件事都讓我痛恨。所以我就自己想辦法做了一個工具,最後命名為 Audiofile丟到市面上。我並不熱愛音樂蒐集,也不熱愛開發軟體(當時才剛學而已)。我也沒有開一間軟體公司的願望。我就只是看到一個需求,然後設法滿足它,謹此而已。這沒什麼不對。之後也基於類似的情況,開了現在這間公司Basecamp。

老實說,就算到了今天,我也沒說總是很愛我的工作。那些文書工作,那些報告,伴隨公司成長所帶來與日俱增的責任與瑣事。這些事都讓我覺得很煩。不過經營Basecamp對我來說還是比做其他事好。我覺得我做得不錯。每天都要作一些需要創意、很有挑戰性的工作。我一直覺得讓專案管理工具越變越好,是一個值得、能有所回報的理由。每天都能跟這些厲害的同事一起工作也真的很棒。

如果要我上講台發表什麼勵志演說,我會說,如果你想成功、想對世界有貢獻,你需要對你在做的事情有某種內在動機。你必須樂於把時間花在那上面。對於它的喜愛有可能在日後增加。如果真的那樣,那很棒。但不需要一開始就熱愛它。光是渴望一個還不存在的東西,就足以讓你成功。

(本文翻譯自 Do you have to love what you do?,得到 Jason Fried 親自授權。)

provoke

批評 Active Record 的13個論點:最好用也最危險的 Anti-pattern

現在很多框架都內建active record pattern來幫助開發者建立application。

乍看之下很自然,實際上它卻違反了一大堆傳統的軟體開發知識。

這會讓工程師在學習時陷入下列困惑:

為甚麼一堆書上的原則(principles)、模式(patterns)、手法(practices),都無法在實務上應用?

接著導致一個更嚴重的認知失調:

是我的開發方法不對嗎?還是這些傳統知識全部過時了?

事實上,active record pattern還真的跟傳統開發知識充滿衝突。

為了點出那些衝突,這篇文章會從傳統開發知識的角度,對active record pattern提出多項批評。

什麼是Active record pattern?

從Rails的ActiveRecord,到Laravel的Eloquent,許多框架都內建active record pattern。

各框架對active record pattern的實作都很華麗。除了直接操作資料庫、充當domain object的功能之外,還提供relationships、自動更新created_at/updated_at欄位等等多種功能。

一般認為active record pattern這個名字由Martin Fowler所發明。

根據他的定義,一旦類別同時處理兩件事情:

* data access logic(處理資料庫相關操作)
* domain logic(處理app的商業邏輯)

就算是實作了active record pattern(下文簡稱AR)。其他華麗的功能都是框架額外提供的。

AR會讓物件同時「提供資料給別人用」與「提供行為給別人呼叫」。

正是因為這點,就傳統開發知識來看,至少可以對AR做出13種批評。

下面會將這些批評分成3種層面來看,然後一一列舉它們:

* 原則層面(Principles)
* 模式層面(Patterns)
* 手法層面(Practices)

(注意:許多批評其實是指同一件事,只是換個層面,或是換個方法描述而已。)


原則層面 (Principles)

1. 違反物件導向(OOP)精神

物件應該將資料對外隱藏,將行為對外開放(hidden data, expose behavior)。

而承載資料的資料結構會將資料對外開放,並且不具有行為(expose data, no behavior)。

AR物件因為同時身兼兩者,導致你從根本上面臨矛盾。

你永遠抓不清分際何在:該讓它所有properties都對外隱藏嗎?還是讓它一個method都沒有?分別該做到什麼程度呢?

2. 違反Single Responsibility Principle

AR負責了data access logic,導致資料庫schema一改,程式碼必須跟著修改。

AR負責了domain logic,導致app的商業邏輯一改,程式碼必須跟著修改。

OOP知名五大原則SOLID中的SRP被AR直接打破:一個類別應該因為且只因為一個理由而修改。

3. 違反「Tell, don’t ask.」原則

「Tell, don’t ask.」原則指引大家OOP開發應該「告訴物件去做什麼」而不應該「跟物件拿資料出來操作」。

偏偏AR物件是如此方便易用,你幾乎一定會從外部直接取用它的資料。

4. 導致Database Oriented Development

應該圍繞著產品本身領域知識去開發的domain driven/business oriented開發方法,變成以database為中心在開發。

開發者在開發過程中,不再對領域知識念茲在茲,而是滿腦子想著database schema。

如果使用的Active Record Pattern甚至實作了toJson這類的函式來幫你把entity內容轉成JSON格式的話,下場就更誇張了:連front-end的開發人員都得跟著滿腦子database schema。前後端分離程度更低。

5. 導致測試時碰到資料庫

傳統上會說測試時應該只測商業邏輯,別碰資料庫,因為碰到資料庫會跑很慢。而且兩者應該分開來測。

但是用了AR會讓測試很難不碰到資料庫。

框架甚至直接提供fixturesModel Factories來協助這件事,毫不避諱。


模式層面(Patterns)

6. 無法用Constructor Injection

你無法實作這個dependency injection模式來設計類別:

因為你的建構式被拿來處理data access logic,因而放棄domain logic了。

7. 導致God object

建出來的entity,會很容易把code一直往裡面放,最後變成上千行、負責一堆功能的God object:

也就是Rails社群流傳的「Fat model, skinny controller」。

8. 導致「貧血的領域模型」(Anemic Domain Model )

如果你為了避免entity內含過多商業邏輯而將code抽離到別的地方(例如:做了一堆service objects),那很容易變成相反的狀況:entity本身幾乎不含什麼code。

例如這樣:

9. 做不出Rich Domain Model

理想狀況是隨著app的商業邏輯越變越複雜,你的domain model會因為各種分工(由多個物件來對外負責各種behavior)而越變越豐富,最後成為漂亮的rich domain model。

非常遺憾的,因為使用了AR的關係,類別的設計被綁死在schema上,因此你很難做出rich domain model。

你要嘛會做出God object,要嘛會做出Anemic domain model。

也許會有人嘗試將data access logic與domain logic分離來克服…

奉勸千萬別這麼做:根本是放棄AR的優點去硬搞,最後變成四不像。

10. 質變的Repository Pattern

將CRUD操作(persistence logic、query logic)封裝的經典模式repository pattern,因為AR而產生質變。

舉例來說,你可能看過有人這樣設計repository class:

接著將repository用dependency injection放進controller或是service object內。

乍看之下沒問題:確實因此可以在測試時抽換repository object成測試專用的object。

但應該「負責persistence logic」的repository,實際上居然只是叫參數物件自己save自己。

這可稱不上優雅。

質變後的repository退化成只負責查詢功能的query object。

11. 質變的Domain Driven Design

Eric Evans寫的Domain-Driven Design一書教導了一種圍繞領域知識的軟體開發方法。

很悲慘的,如果你用了AR,這本書你將很難看得懂,也很難應用。

DDD從最基本的Repository Pattern就已因AR導致質變(參考10.),再加上entity間的關係通常會被AR直接根據schema建好,Aggregate也變得無法封住內部物件,更不用說domain model根本建不好了(參考9.)。


手法層面(Practices)

12. 濫用public properties

OOP課本翻沒幾頁就會提到getter/setter的觀念,並且反對直接把properties設成public。

AR因為同時負責data access logic,很自然會讓人直接對properties操作:

13. Atomic operation困境

為了確保Database內的資料正確性,有時你需要用到lock機制或是transaction機制。

試問下列這兩行code該放哪呢?

放在entity的話,代表Order/Item/User其中一個類別會直接操作其餘兩個類別 => 容易導致god object。

不放在entity的話,就必須放在外部(例如service object或controller) => SQL語法不再被封裝在底層,也不被封裝在entity內,而外洩到更上層了。

結論

Active Record Pattern方便、快速開發的特性,讓它具有非常高的商業價值。

這也是Rails之後一直到Laravel等等各大框架都實作它的原因。

愛用Active record pattern跟反對active record pattern的人常常有所爭論,幾乎成了現代開發vs傳統開發方法的戰爭(例一例二)。

使用它來開發沒什麼問題,但千萬要留心它與哪些傳統開發知識有衝突。

享受它的優點,理解它的缺點,才能在現代和傳統開發知識的衝突上,找到屬於自己的平衡點。

(完)



下面是我在公司新開發的一個討論plugin。歡迎用它來反映您對AR的看法。

順帶一提,server-side是用Laravel 5所寫成:背後的Eloquent,正是實作active record pattern。

(Photo via Don McCullough, CC licensed.)

fancy

簡單聊一下 One-way data flow、Two-way data binding 與前端框架

新手在學完JavaScript基本知識、離開新手村之後,很快就必須面對前端框架。

這些框架常會號稱是one-way data flow或是two-way data binding。

這兩個名詞究竟代表什麼呢?一定要有框架才能做到嗎?一個框架只能是one-way或是two-way嗎?

這篇文章會一次回答這些觀念問題,並且分別檢視一次Backbone、Angular、React三套框架。

用詞澄清

在開始之前,先針對用詞做兩點澄清。

澄清一:「data flow」跟「data binding」指的是同一件事。

換句話說,one-way data flow就是one-way data binding;two-way data binding就是two-way data flow。

這件事其實Facebook的React官方文件就有提到:

React’s one-way data flow (also called one-way binding) keeps everything modular and fast.

澄清二:這兩個詞是指稱某種行為,而不是指稱具備某種功能。

舉例來說,下面這種句子是不嚴謹、充滿誤會的:

* 某某框架「是」一個 one-way data binding 框架。 → 不嚴謹的句型,盡量少用。

正確的描述方法是這樣:

* 某某框架讓人「很容易做到」one-way data binding。 → 正確句型。

溝通上應該避免用「是」句型;用「很容易做到」句型才正確。

使用正確的句型有助於理解這兩個詞。這部份後面會再解釋。

Data model

data binding指的是UI元件和data model之間的互動。

在談data binding之前,我們先從data model的觀念開始談起。

沒有data model觀念的JavaScript/jQuery寫法

新手最直覺的作法,就是用JavaScript/jQuery不斷操作HTML元件,讓畫面上顯示他要的效果。

不同元件之間需要溝通時,就每次都去觀察HTML元件的內容,將需要的資料找到,接著處理。

舉例來說,做一個TODO list時,就用ul元件包住一堆li元件,每個li元件代表一個待辦事項。

需要增加/減少/修改事項時,就用JavaScript/jQuery想辦法找到目標li元件,然後處理它。

那麼要把所有事項內容用alert跳出來,或是用HTTP POST丟給後端怎麼辦?

那就用JavaScript/jQuery把li的內容分別抓出來,整理成字串丟給alert顯示,或是另組成form元件submit出去(用成Ajax丟出也可以)。

簡單來說,就是data本身存在於UI元件(HTML元件)之中。每次需要data就去分析一次UI元件。

這種作法簡單、直覺,在UI單純時可以這樣寫,但當頁面上的UI元件多、互動又複雜時,程式碼很快就會變成一團混亂。

有data model觀念的JavaScript/jQuery寫法

有經驗的工程師很快就會想到separation of concerns原則,將data本身獨立成自己的模型(本文稱為data model),每次顯示就根據那個data model去render出UI元件即可。

例如說像這樣(todos陣列就是data model):

然後在button元件或是li元件的onclick事件裡面操作todos陣列,就算是操作data model了。

此外,記得在所有關於data model的操作內呼叫renderTodoList函式。

像這樣任何操作都從data model出發,最後再render的作法,可以讓data model跟UI元件保持某種對應關係,讓程式碼更好維護。

有經驗的前端工程師,光靠這種技巧,再搭配Handlebars之類的模板系統,就可以寫出十分漂亮的程式碼。

例如這份:jQuery TodoMVC source code

可以反過來寫嗎?

這個時候,就有人疑惑了:要讓data model跟UI保持對應關係,可不只這種方法。

為何要從data model出發再render出UI?何不從UI出發再generate出data model?

每次變動過HTML都去更新data model不可以嗎?

其實可以,那就會像這樣:

而清空列表的程式碼會變這樣:

任何操作都從HTML(UI)出發,最後再generateDataModel的作法,一樣可以讓data model跟UI元件保持某種對應關係。

Data binding

有了data model獨立於UI的觀念之後,我們會發現上面兩種作法代表兩種資料的流向(data flow):

* 從data model出發,每次更新就同步更新UI(甲方向)

* 從UI出發,每次更新就同步更新data model(乙方向)

前端框架與data binding的關係,就是框架本身能否讓人很容易就做出甲方向或是乙方向的效果。

能輕易做出其中一個方向,就算是達成one-way data binding。

能輕易同時做出兩方向,就算是達成two-way data binding。

以前面Todo list的兩個範例來說,其實都做到one-way data binding了,但是我需要去多寫一個陣列、幾個函式、用了onclick事件、手動去loop過每個待辦事項、還要記得每次去呼叫函式,花了點功夫(共做5件事)才做到one-way data binding。

所以我們不會說JavaScript本身有data binding機制,也不會說jQuery是一個讓人很容易做到one-way data binding的套件。

這也是最前面「澄清二」提到的避免使用「是」句型的原因:

* 說「jQuery是一個提供one-way data binding的套件」是錯誤的,因為做起來不太容易。

* 說「jQuery不是一個提供one-way data binding的套件」也是錯誤的,因為還是做得到。

* 說「jQuery要做到one-way data binding不太容易」就沒問題,邏輯上正確。

如果每次更新data model就call renderTodoList,再加上,每次更新UI就call generateDataModel,那甚至做到了two-way data binding。(實務上大概不會有人這麼做。)

說明完這兩個詞為什麼是在指稱行為而非特性,然後解釋了句型上的正確用法之後,讓我們來解讀各大前端框架的特性,以及用正確的句子評論它們。

Backbone.js

參考這份程式碼:http://jsfiddle.net/Xm5eH/9/

甲方向data binding

設定data model更新時要call render函式:

運用Underscore.js提供的模板系統:

在render函式用jQuery正式將模板系統的輸出塞進HTML裡面;

官方提供的幾個機制就達成data binding了。

這也是為什麼很多人說「Backbone.js是一個one-way data binding框架」的原因。

(其實正確說法應該是:「Backbone.js是一個讓人很容易做到 one-way data binding的框架」。)

乙方向data binding

先去監聽UI元件的變化,一變化就call update函式:

在update函式去更新data model:

評論

乙方向data binding很有爭議,也造成Backbone.js社群的一些誤會與爭論。

在某些人看來,這樣夠輕易就達成乙方向data binding了,因此「Backbone.js是一個two-way data binding框架」。

在另外某些人看來,這只是去監聽UI內容的變化然後手動更新data model而已,因此「Backbone.js不是一個two-way data binding框架」。

在最後某些人看來,上面甲乙兩個方向都是一堆手工設定達成,需要更優雅的去綁定兩邊才算數,所以又做了Epoxy.js這樣的套件。

這也再次說明了為什麼要避免使用「是」句型:它帶給人們混亂、誤會與雞同鴨講。

就說你認為「Backbone.js容不容易做到one/two-way data binding」就好了。

因為「容不容易」是一個主觀問題,聽的人各自判斷,誰也不需要說服誰。

Angular

參考這份程式碼:http://plnkr.co/edit/GxqBiOoNFuECn55R4uJZ?p=preview

甲方向data binding

把資料設在$scope底下:

使用{{}}的模板syntax:

乙方向data binding

利用ng-model這directive就完成了:

評論

甲乙兩方向都很容易做到,Angular也一直以此為賣點。

這就是一談到two-way data binding,人們就想到Angular的原因。

話雖如此,還是老話一句:不要說「Angular是一個two-way data binding的框架」。

說「Angular讓人很容易做到two-way data binding」比較好。

React

參考這份程式碼:https://jsfiddle.net/reactjs/n47gckhr/

甲方向data binding

用props的方式設定資料:

或是用state的方式設定資料:

然後在render函式裡面寫獨特又優雅的jsx語法:

一律由state或是props一層一層將data往子元件傳,超級優雅的甲方向one-way data binding寫法。

這就是一談到one-way data flow,人們就想到React的原因。

乙方向data binding

寫一個監聽變化的函式,一監聽到就去更新data model:

然後設定好onChange這個prop即可:

評論

乙方向data binding做得到,官方甚至提供Helpers使用,還用Two-Way Binding Helpers描述它。

但是實務上通常會採取某種程度的Flux架構(永遠經手action和store去更新UI),因此不會使用這種Helpers。

所以大家都說「React是one-way data flow」而不會說「React是two-way data binding」。

話雖如此,換個方式,說「React會強迫做到one-way data flow」比較好。

至於React跟two-way data binding的關係就不重要了。

不過就是個做得到但實務上不常用的手法。你覺得是就是,你覺得容易就容易吧。

結論

透過這篇文章的脈絡,你會發現隨便套個模板系統,然後在每次data model更新時用下面任一方式重新render出UI,都算是甲方向data binding:

* 全手動(jQuery範例的呼叫render)
* 半手動(Backbone範例的設定模板與model on事件)
* 自動(Angular與React)

至於乙方向data binding就更多元了:有的看起來像是全手動更新,有的看起來像是半手動監聽事件然後update model,有的看起來像是宣告bindings即可,有的寫個ng-model的directive就完成。

光是甲或乙方向data binding的定義就如此鬆散,自然one-way或是two-way的爭論會應運而生。

其實,在挑選框架的時候,不管是標榜one-way或two-way的框架,背後終究都只是資料傳過來傳過去而已,差別不過在於框架本身替你做掉多少事情。

然而,框架替你做多做少都不是重點,畢竟做多本身也代表入門越困難、convention越多。

只要整體用起來順手、好理解、好維護,那麼任何一種寫法都是最好的寫法。

(完)


參考資料

http://stackoverflow.com/questions/13504906/what-is-two-way-binding

http://stackoverflow.com/questions/30590170/simple-practical-example-for-two-way-data-binding-in-angularjs

(Photo via TheGiantVermin, CC licensed.)