about 6 years ago

paperclip.io 是我與 zhusee 最近奪得 Facebook World Hack 大賞 的作品。T 客邦在 Facebook 公布大賞得獎名單後 (10/15) 第一時間採訪了我們

獲得 T 客邦 授權,將 12 道採訪內容轉貼回來我的部落格。


1、為什麼會選擇開發 Paperclip.io 這樣的服務?你們發現了什麼樣的需求?這個點子是怎麼來的?

我平常在使用 Facebook 時,到不錯的連結或頁面就會順手按讚。但是,按完讚之後過後想找自己前幾天曾按過什麼連結,卻很麻煩。Facebook 一直沒有一個入口介面可以讓你找之前讚過什麼。我認為這件事造成我相當大的困擾,就覺得應該要有一個開發者來寫一個這樣的 App 幫助大家....但很明顯應該是沒有人要寫,於是我就打算自己寫。

剛好 Facebook 舉辦這次比賽,我就打算拿來當這次的題目。

2 、在 Facebook Developer World HACK 2012 裡面,因為每個站都是一天的活動,而且比賽時間只有幾個小時,在這麼短的時間內,你們做了什麼準備,讓作品可以贏得比賽?
選題

首先,我認為是「選題」吧。這是一個「夠小」而且「解決真正大眾困擾」的題目。如果我們選擇進行這個題目。可能題目賣相就會高一點。(我猜)

專注

其次,我認為是「專注」。因為這個「題目」夠小,我們可以把我們的火力集中在於完成核心的實作。主要核心就只是兩隻 API 爬蟲 和網頁爬蟲。這兩個部分很快就寫完了,我們剩下的精力都在調整介面的順暢度。

賣相

第三,調整 demo 時的賣相。因為上台前需要寫投影片和 live demo 自己的作品,demo 只有短短的 5 分鐘,我必須在這麼短的時間內讓評審和其他的參賽者,一目了然知道我們的服務在做什麼,解決了什麼問題。於是我註冊了一個假帳號負責 demo,這裡面的內容是我精選過的,可以看完之後就了解我們在做什麼(我本人 like 過的資料其實很雜亂)。 讓評審能夠一下子理解我們想要作什麼,解決了什麼問題。我想也是拉高勝率的一大原因。

炫技

第四,介面炫技。zhusee 是一個很強的前端工程師,我經常提了一堆的點子,他馬上就能用很炫的方式實作出來。我們嘗試在 demo 前能夠讓所有的介面非常的流暢(即便是等待時間)。另外也花時間做了高難度的首頁特效,吸引目光焦點(畢竟是 Hackathon….當然要炫耀一下)。

3、在整個活動過程中,讓你們印象最深刻的事情是什麼?

參賽的台灣隊伍都很強很有創意。我不知道原來大家會拿 Facebook API 惡搞出這麼多的創意。比如說 Memory Millionaire,我就覺得他們的點子相當有意思。

在宣布三大獎項之後,我們其實一度很失望…一度以為自己落選了。

直到評審宣布評審特別獎,我們發現得獎名單也沒有我們(這也落選會讓我受到很大打擊XD),我們才猜測可能是我們拿到了首獎!!

4、開發 Paperclip.io 總共用了多少時間?在這個過程中,有沒有遇到什麼樣的難關?

其實在比賽之前,我有試寫了一個很小的 prototype,練習 FB API 的存取,但是,成品很糟,存在很多問題。但是因為已經熟了 FB API,我大概覺得這個網站主體架構,在比賽的時限之內,我是有把握可以做完的。

原始設計存在不少問題,用改的拿去比賽太麻煩了。我決定到現場重寫一遍。我們從上午九點到會場,就一直馬不停蹄的在寫 code,寫到開始 demo,所以我也不清楚我們整整寫了多久。

不過中間的確有遇到幾個重大難關:

首先是網頁的爬蟲演算法優先順序問題,當初在設計時,設計的 worker 演算法不好,會造成前面使用者資料沒抓完,後面使用者資料就無法開始進行。這在使用者體驗上會非常不好,因為使用者會覺得網站壞掉了。於是我們花了幾乎整整一個小時打掉原先的 worker 重新設計。

再來是搜尋索引效率的問題,我們嘗試讓這些連結是可用關鍵字被搜尋的,但是資料是分批分批抓進來的,所以會有異步索引的問題。我一直都無法好好的解決這個問題,最後心一橫,不解決了。直接用 MySQL 全文檢索…

不然我們可能會被迫在上台前拿掉這個 feature(但我認為搜尋是一個很大的賣點)。

5、Paperclip.io 隊伍的成員有兩位,你們之間如何分工?

我(xdite)主寫整個網站的架構,設計爬蟲、梳理流程、製作投影片以及上台簡報。zhusee 實作強大的視覺特效以及繪製精美的 UI。我會先把需要加工的頁面在第一時間寫出來,交給 zhusee 操刀設計,我們用 git 控制程式碼,基本上可以做到全速各寫各的,毫不干擾。

6、在 Paperclip.io 的功能裡,有個 Recipes 的分類,讓人很好奇為什麼會特別把食譜做個分類出來?又,書本、食譜這兩個分類的內容,是怎麼去判斷的呢?

在把整個網站初稿寫出來之後,我們認為這個網站如果只有連結實在太單薄了....

於是我們決定加一些分類。分類就是按照 og:type 去分,我們發現有幾個 type 做出來的視覺效果不錯,如:Youtube、食譜、Github …於是我們就決定把這些功能加進去了。

7、在得獎之後,是不是打算升級 Paperclip.io 網站的硬體資源,讓匯入的資料可以更快跑出來?

這個網站吃的資源真的很驚人,我不確定我能不能一直養著它…

說到這個,其實在現場邀請一些朋友幫忙測試時,我們就發現一些效能上的問題了。於是,我們還在當場做了一個非常大的賭注,就是現場刷卡升級 linode 機器(伺服器)。這時候已經快要接近 demo 時間了,要是升級當場出了什麼問題,或者機器來不及當場升級完畢,我們可能就直接開天窗了… 還好這件事並沒有發生。

8、對於想要參加類似 Hack 活動的人,有什麼樣的建議?

我參加過好幾次 Hackathon,得過兩次獎。一次是 2008 年的 Yahoo Open Hack Day(那次是公司同事一起出去比賽,作品是「和多繽紛樂」,得了亞軍)。一次就是 2012 年的這次 Facebook World Hack 得到首獎。

在這好幾次的參賽經驗中,我得到幾個寶貴經驗:

(1) 好的題目很重要

評審希望得獎的題目是 valuable 的。搞笑、諷刺、主題模糊的題目,一定不會得獎。(我在 2009 的比賽跟一群大神等級的朋友合作做了一個精美的搞笑網站「我是專家」,但是…我們沒有得獎)

(2) 賣相非常重要

有些 Hackathon 比賽,甚至投影片比網站重要…。有某幾次的 Hackathon 比賽,竟然是投影片贏了網站。這讓我覺得很不公平也很無奈。但我也理解到比賽要贏就需要賣相的現實。 雖然這次 Hackathon 大會是要求需要提供 source code 以證明不是投影片的實戰比賽,舞弊不至於發生。但我還是認真的投資了快要一個小時在調整假 demo 帳號、截圖、寫投影片.....

9、接下來還會參加其他 Hack 活動的計畫嗎?

暫時沒有。因為我們忘記報名今年的 Yahoo Hack Day 比賽。但我想沒有關係,今年拿到這個獎就值得了…

10、如何把 Bootstrap 改得這麼好看?

我們用了一些網路上的免費素材,比如說

簡單抽換了一下材質,然後用了一點 CSS3 技巧,提升介面質感。這些都是我們平常開發時就相當熟練的技巧,用得很自然。

11、從零到完成作品,用了很短的時間,這是怎麼做到的?有什麼樣的密技?工作進度如何管理?是有什麼樣的開發好習慣嗎?

我想主要是幾個重點:

(1) 時間管理

我參加過很多場 Hackathon。大概知道寫 code 時最容易踢到什麼鐵板。或者開發中最容易遇到什麼鬼打牆的事情。

比賽通常只有短短的幾個小時,所以你要把最浪費時間的部分想辦法節省掉。比如說:如果當天再討論 idea,你的時間就很有可能不夠用。網站需要佈署,佈署需要測試,所以最好有只要一鍵就能 deploy 的環境。domain name 全球生效需要時間,所以 domain name 最好先買。

現場再搞這些事,網站鐵定作不完。何況最後至少要留半小時寫投影片....

(2) 知道什麼該放棄

因為時間不夠,所以其實很多功能,不夠時間讓我們寫到夠完美夠好。於是對自己在開發任何元件時,都要設定 deadline。如果一定時間內(15 min' 30 min)寫不完,就要放棄,或者是改採其他 solution。

(3) 平常要有自己的 best practices

作網站的時候,我們知道很多工夫都是重複的。比如說作網站一定要有一個網頁主框架、一個 Facebook 登入系統、一個系統管理介面,一些常見的分享功能。

這些事情都小,但是堆起來還是很花時間。如果比賽時,這部分的時間成本若是 0,我們可以把更多的時間花在寫核心功能上。像我上禮拜釋出的一個 app 產生器 Bootstrappers

這個 Bootstrappers 其實就是我這次比賽時用的大砲,它可以讓你一鍵就產生一個網站雛形,然後馬上開始刻程式。所以當別人還在討論要作什麼時,我這部分已經作完了....

而我和夥伴已經一起工作將近三個月,彼此有不錯的默契。我們在寫 code 時,了解彼此寫 code 的習慣,於是接力對方的部分,速度就非常快。而我們更用了 git 這套程式碼版本控制系統,可以做到各寫各的,不會干擾。

12、如果 Facebook 邀請你們加入當員工,會進去嗎?

會慎重考慮。

 
about 6 years ago

Paerclip.io

Facebook visit

Hi, everyone. I am happy to announce the new service I recently built at 2012 Facebook World Hack Taipei : 「Paperclip.io」. Not only won the “Best Overall” prize in Taipei. We also win the Grand Prize of World Hack .

News here: World HACK Recap and Winners

====

We're pleased to announce the Grand Prize winners, whose projects stood out from the many high quality apps. These teams have won a trip to Facebook headquarters in Menlo Park, where they will meet with members of the Facebook engineering team:

The Paperclip.io team, from Taipei. Paperclip.io indexes and sorts the things you've liked on Facebook and across the web, so you can easily browse your likes by category or by the date you liked them.

The Chained Story team, from Buenos Aires. Chained Story is a web version of a storytelling game in which players take turns adding a sentence or paragraph to a story. The completed story can be published to a user's timeline.

The BoostMate team, from Moscow. BoostMate analyzes your social graph and the connections you have with all your friends, producing a ranking of who you're closest to, who you interact with least, and whether those interactions were positive.

====

各位好,前陣子在 Facbook World Hack 奪下台北站首獎的作品paperclip.io。我們很高興的宣布,這個服務再次奪下了 Grand Prize of World Hack 。獎品是前往 Facebook 總部一遊。

我們很高興能為台灣爭了一口氣!謝謝大家一路上的鼓勵!

新聞連結在此:World HACK Recap and Winners

 
about 6 years ago

Bootstrappers Demo

這是我本週剛釋出的一個 Gem: Bootstrappers。是一個 Rails 專案產生器,特色是內建馬上套好的 Bootstrap theme 和 其他好東西。

Bootstrappers 的開發緣由

身為一個職業開發者,我開啟一個新專案的機會實在太高了。但是,你知道的。每次要開發一個專案,無非就是 rails new project_name。然後打開 Gemfile,添加一些常用 Gem,再修改 application.html.erb,塞塞設定,填填 CSS,修修 HTML。

Bootstrap 以及其他 rubygems 是很方便,但是把他們組起來還是非常花時間。次數多了,我就覺得這樣的初始化動作實在很煩。

於是,我第一次的嘗試,就是作一個空專案,把我平常的 best practices 和 templates 都丟進去。如果有開新專案的需求,再 copy 過去。

但是,隨著時間更迭,我又發現更煩的事了。就是我還是得花上一堆時間改 namespace 與 setting。而且,裡面內建的 rubygems 會過期,等於還要花時間 ugprade 這些 template。而最煩的還是:Rails 本身自己也會過期!而要升級的小版本,自己可能還要補一堆 config,而這些變動真的很難追蹤。

於是,最後我下定決心要來研究 App Generator 到底要怎麼寫。我實在受夠了每次的 c/p + modify 了。

(之後我也許會寫一篇如何製作 App 產生器的文章 )

最後 Bootstrappers 就這樣誕生了。

內建好康

這個專案裡面目前內建了以下這些 Gem 以及相關 Template :

Powerful Features

特點如下

  • 現成套好的 Bootstrap Theme (application.html.erb)
  • 搭配的 Bootstrap Helper,快速兜出表單、選單、按鈕、Dropmenu、ajax modal、alert、breadcrumb 等等…
  • 套好的 WillPaginate (with bootstrap style)
  • 套好的 SimpleForm (with bootstrap style)
  • 內建 Devise 會員系統
  • Bootstrap useful hacks (比如 body { padding-top:60px }、dropmenu 自動展開等等)
  • Boostrap override best-practices
  • MagicEncoding : Ruby 1.9 自動添加 utf-8 宣告
  • 自動掛上 Compass
  • SeoHelper : 自動幫你的網站產生 page description / page keywords
  • Open Graph : 自動幫你的網站產生 og description / og_image
  • Facebook JS、Google Analytics
  • Capistrano / Cape
  • Asset Pipeline 加速器
  • ….etc.

一些我平常開發專案時,累積出來的 best practices。

有了這個武器,你現在可以使用 bootstrappers project_name 這個指令,一鍵瞬間就產生出一個不錯的 app,而不用擔心套版問題以及一些基本的網站優化問題。

歡迎 現在就試看看

Bug / Pull Request

https://github.com/xdite/bootstrappers/issues

歡迎各位回報錯誤,或者提交 Pull Request。當然,如果能夠直接提交 Pull Request,是最感謝的。

我還會繼續把一些還沒丟進去的 best practices 繼續整合進去。如果各位有興趣幫忙的話,歡迎查看 TODO.md。

https://github.com/xdite/bootstrappers/blob/master/TODO.md

 
about 6 years ago

在我的前一篇文章「Specification by Example - 團隊如何交付正確的軟體」,我提到了一件在工程師界,人人皆知卻不願意說出口的「秘密」:

「工程師竟然時常比他們的雇主或PM,更了解它的生意邏輯與流程」。
「客戶在它的 Spec 裡面卻指定了完全不可行或者是成本效益極低的作法」。因為簽了合約或領了老闆的薪水,我們被迫在明知不可而為之的狀況下,進行了一個徹底失敗的專案。」

如果你不是身處於工程師這個圈子的話,若無意中聽到這一件事,通常會覺得這群人相當傲慢。這群人不負責執行 Bussiness Development,怎都可大膽有此感想?

一開始,我也對這個「觀察」是存疑的。因為一開始時擁有這個觀察時,我還算是個很菜的工程師,這個觀察對我來說應該是錯覺。但隨著生涯中經歷過許多專案的角色。在專案中,我屢屢嘗試著尋找能夠反駁這個觀察的蛛絲馬跡。但最終都以失敗告終。

而後來更輾轉得知,這幾乎是這個圈子內「不能說出口的秘密」。我才同意這也是真的結論。只是我還是找不出 理論 / 反證。

直到最近,我才從幾段討論中,赫然領悟這件事情也許可能是有一套理論可以解釋的。

一段是從 @yllan (台灣知名 Cocoa 開發者,Nally 作者)在的 Facebook 的 post

====

Steve Jobs 說他很喜歡一個比喻。他以前在 Scientific American 上看到一篇文章,研究各種動物運動的效率,最強的是兀鷹(同樣的運動使用最少能量),人類排名普普通通,可能排在所有動物的三分之一左右。

有趣的是,那篇文章也研究了「騎著腳踏車」的人類,而騎腳踏車的人其能源效率把所有動物遠遠甩在後頭。人類製造工具大幅拓展自己的能力。而 SJ 把「電腦」比喻成「心靈的腳踏車」。

他又認為每個人都應該學程式,因為你在教電腦事情的時候,其實是在釐清自己的思考。這正和 Knuth 的名言「A person does not really understand something until after teaching it to a computer」不謀而合。

====

一段是我在跟與朋友的網路爭辯中,脫口而出寫出來的一段話(我常常從辯論驗證中,突然找到靈感,這些感想甚至是我自己也不知道為何會脫口而出的絕妙結論):

====

xdite : RD 會知道你的生意
xdite : 能不能成
xdite : 遠比你自己早知道
xdite : 因為他們會直接先面臨
xdite : 邏輯能不能實作
xdite : 我們會推測就是
xdite : 1. 合理
xdite : 2. 成本(要作多久)
xdite : 3. 效益
xdite : 剛好就是一個事業能不能做的基礎
xdite : 只是很多人誤以為
xdite : RD 只會 code .....
xdite : 3. 效益 就是...能不能重複用
xdite : 能不能實作 要作多久 可不可以重複利用
xdite : 都不可以 就會懷疑
xdite : 你是賺三小

====

我終於理解,為什麼 RD 會提早知道這個生意能不能成。這根本的原因就是因為他們便是嘗試教電腦事情的第一線人員。也就是會先面對「釐清」實作上「合不合理」的第一個人。

程式邏輯是非常現實的,若這件事不合理,RD 就會面臨「無法實作」的困境。這是其一。

其二:只有實作的人,才會知道這件事到底要作多久。RD 也許有樂觀病,往往他們告訴你需要實作兩週,但往往真實需要的時間也許是四週。但如果他們告訴你,需要半年這麼久,或者是根本作不出來,沒有完工的可能性。那可能這件事就不可能發生 -- 起碼在他們手上。

同時也應該要注意的是,這直接反應了成本的爆增。有時候,這甚至不是換一批執行團隊可以解決的問題。RD 正在試圖告訴你,執行代價高昂,你最好不要白花錢。因為很現實的,他也不想要白花時間…

其三:所有 RD 都非常討厭寫 event code。所謂 event code 就是只用過一次的 Code(因為某些特殊事件,如廣告、行銷活動,只執行過一次即扔的產品)。這類型的程式碼,往往無法被重新用在下一次的類似事件中,只能重新撰寫重新來過。這對任何重視自己心血結晶的人,重新來過是非常累人非常惱人的事。

而這對生意執行面來說,是非常高昂的沉沒成本。一再發生,甚至是不應被允許的行為。

小結

任何賺錢生意無非都是幾個簡單原則:首先,先觀察到一個合理需求。接著針對這個需求設計出一個可執行的解決方案。重複,證明解決方案可以被重製,可以得到收益。接著壓低執行成本,演化出一個可以獲利的模式。而在這段過程中:

  1. 合理實作
  2. 時間成本
  3. 重複效益

是至關重要的。

世上從來就不存在這麼一個假設:「覺得自己有一個偉大 idea,接著偉大的事情就會發生」

而在整個模式的發生過程,RD 正是日日夜夜都要面對這個挑戰的第一線執行者。

如果他們很坦白的告訴你這個想法很蠢,也許這件事就真的很蠢,你應該停下來聽他們怎麼說…

 
about 6 years ago

這個禮拜終於斷斷續續用了空檔時間讀完了一本買了卻一直沒時間坐下來好好研究的書「Specification by Example

對岸的圖靈系列最近也出了這本書的中文版:「實例化需求」。如果你想要觀看這本書的書評,InfoQ 上有一篇不錯的文章:「《實例化需求》採訪與書評」。

這本書給我一種很奇妙的讀後感,因為書中既沒有程式碼,也不介紹任何工具,甚至實際軟體例子也很少,篇幅最多的甚至是模糊的團隊訪談。

但讀完了以後,卻讓我在軟體開發上流程上有了更大的啟發。

交付錯誤的軟體的原因

我是一名職業的軟體開發者。前前後後寫過的軟體專案也有 50 個, 60 個。目前也以開發軟體為生。在我的職業生涯裡面,其實我有一個從來沒有跟人講過的秘密困擾。這個困擾,我相信許多同業們可能也有。那就是 --- 一個專案開發下來,「我們竟然時常比我們的客戶或 PM,更了解它的生意邏輯與流程」

但這個問題帶來更大的困擾是:「客戶在它的 Spec 裡面卻指定了完全不可行或者是成本效益極低的作法」。因為簽了合約或領了老闆的薪水,我們被迫在明知不可而為之的狀況下,進行了一個徹底失敗的專案。

技術很好,團隊也強大,產品也有市場。但還是失敗,因為 -- 「交付錯誤的軟體」。

軟體工程沒教的課題: 交付正確的軟體

市面上有很多書,教人如何敏捷開發,教人測試驅動開發(TDD)。它們可以帶給開發者的好處是可以利用這些技巧將工程時間大幅縮短,降低軟體內發生 Bug 的頻率。

這些技巧對於進行軟體專案不是沒有作用,因為早點完工(把功能實做出來),專案早點失敗,專案可以及早軸轉到較接近成功的方向。

對於正在營運中的公司,內部專案早點失敗,及早軸轉到較接近成功的方向。往往是可接受的。因為總體目標是儘快交付到貼近正確方向的軟體。

但對於目標是交付一個軟體的專案,「交付錯誤的軟體」卻往往是糾紛的起源。但卻也是一個千古難解的課題。對於業主來說,他付錢是希望得到一個「正確的軟體」。但於對於被委託的開發者也往往有苦難言,因為他們得到的指示是「按照業主精確的功能敘述去實作軟體」,「正確與否」不是他們的最終責任。而是否「正確」通常往往也得等到上線之後,客戶根據用戶實測反應才能得知(雖然開發者往往是開發階段就往往能猜測出是否失敗的那一群人)。但這從來也不在合約的責任之內。

而這本書也就是在探討這個課題:怎麼樣的軟體流程,才能交付正確的軟體。

大家沒想到的答案: BDD

這本書繞了很多遠路去講解什麼是 Specification by example,但這也是作者的用意:刻意不使用專業定義字眼如「敏捷」、「測試驅動開發」去輔助解釋,避免整個梳理的流程被大家腦海裡面的術語印象所綁架。

但總體來說,這個結論毫無疑問就是 Behavior Driven Development (BDD)。不過這個 BDD 卻跟我當初學到的 BDD ( from Cucumber ) 印象很不一樣。這也是為什麼這次會花上幾個小時謄下這篇心得。

裡面有幾段 quote 我很喜歡,實際擊中困擾的核心:摘錄如下:

「實現範圍(Implementation scope)含有對業務問題的解決方案或達成業務目標的手段。很多團隊在開始實現軟件之前(在此之前發生的一切往往被軟件開發團隊所忽略),期望客戶、產品負責人或商業用戶來確定工作的範圍。在商業用戶明確說明他們的需求後,軟建交付團隊就依此時現。這樣本應該會讓客戶滿意。但事實上,這正是構建產品開始出現問題的時候。

如果軟件交付團隊依賴客戶給出用戶故事、用例清單或其他相關信息,那麼他們其實是在讓客戶設計解決方案。但是商業用戶不是軟件設計師。如果我們讓客戶去界定範圍,那麼項目就無法從交付團隊已有的知識受益。這樣開發出來的軟件是客戶所要求的,卻不是他們真正想要的。

成功的團隊不會盲目的接受軟件需求,將其作為未知問題的解決方案,相反,他們會從目標中獲取範圍。他們以客戶的業務目標為起始,然後通過協作界定可以實現目標的範圍。團隊與商業用戶一起工作確定解決方案。商業用戶專注於所需功能希望達到的目的,以及他們期望由此帶來的價值,這樣有助於所有人了解所需的功能。然後團隊提議一個解決方案,這樣比商業用戶自己想出來的方案更實惠、更快,並且更容易交付或維護。」

「與我一起共事過的商業用戶和客戶,大多喜歡把需求描述成解決方案;他們很少會去討論想要達到的目標,或者亟待解決的問題具有什麼特殊性質。我見過太多的團隊有一種危險的誤解,他們認為客戶總是正確的,客戶要求的東西總是一成不變的。這導致很多團隊盲目的接受客戶建議的解決方案,然後竭盡全力去實現。」

「在構建正確軟件產品的過程中,確定範圍扮演著重要的角色。沒有正確的範圍,其餘的工作只是在作無用功。」

「人們告訴你他們自己認為需要什麼,通過問他們『為什麼』,你可以找到背後的目標。許多組織不能明確地指出他們的商業目標。然而,一旦你獲得了目標,就應該再反過來從已確定的目標上獲取範圍,可能你會丟棄掉原先假定出來的範圍」

一個實際的例子:對 VIP 免費送貨 的需求

Specification by example 強調的是對於需求,我們必須設計出一個可以被實現的方案,這個方案可以被單獨測試驗證。並且從這個方案與程式碼中演化出 LiveDocument。

書中舉出了一個實際的例子。(整理摘錄)

Untitled

商業目標

12 個月內對現有客戶提高 50% 的重複銷售

實作範圍

我們可以從商業目標中獲取實現範圍。實現團隊和商業投資者一起提出一些想法,然後把他們分成可交付的軟件塊。比方說我們發現一個主題故事是關於客戶忠誠度計畫的。這個故事可以分解成客戶忠誠度管理系統的基本功能和更高級的獎勵計畫。我們決定首先專注在建立一個基本的會員忠誠度管理系統上:客戶註冊一個 VIP 計畫,VIP 客戶有資格獲得特定物品的免費送貨。我們將推遲關於高級獎勵計畫的討論。下面這個例子的用戶故事:

  • 為了能對現有客戶作產品直銷,作為營銷經理,我想讓客戶通過加入 VIP 計畫註冊個人信息。
  • 為了吸引現有客戶註冊 VIP 計畫,作為營消經理,我要系統為 VIP 客戶提供特定物品的免費送貨。
  • 為了節省開支,作為現有客戶,我希望能收到特價優惠的信息。
關鍵實例

一旦團隊開始實現某個特定的功能,我們就可以為特定的範圍產生具體的需求說明。比如,當我們開始作範圍中的第二項 -- 免費送貨時 -- 必須定義好什麼是免費送貨。在協作討論過程中,為了避免運送電子產品或大件物品相關的後勤問題,我們決定系統只提供書籍的免費送貨服務。因為商業目標是提升重複銷售,我們嘗試讓客戶進行多次購買,「免費送貨」變成了「免費為 5 本或以上書籍送貨」。我們要確定好關鍵實例,比如 VIP 客戶購買 5 本圖書、VIP 客戶購買 5 本以下的圖書,或者非 VIP 客戶購買書籍。

接著討論當客戶同時購買了書籍和電子產品時該怎麼辦。有些人建議擴展範圍,例如,將訂單拆分成兩個,只為書籍提供免費送貨。我們決定推遲這個決定,先實現最簡單的。如果訂單中有非書籍的物品,我們就不提供免費送貨。我們加入下面這個新的關鍵實例,之後會再討論。

關鍵實例:免費送貨
  • VIP 客戶購物車中有 5 本書籍可以獲得免費送貨
  • VIP 客戶購物車中有 4 本書及就不提供免費送貨
  • 普通客戶購物車中有 5 本書籍沒有免費送貨
  • VIP 客戶購物車中有 5 台洗衣機時不提供免費送貨
  • VIP 客戶購物車中有 5 本書籍和 1 台洗衣機時不提供免費送貨
帶實例的需求說明

我們從關鍵實例中提煉出需求說明、創建出一目了然的文檔並將其格式化成便於今後作自動化驗證的格式(如下所示)

免費送貨
  • 當VIP 客戶購買一定數量的書籍時,提供免費送貨。免費送貨不提供給普通用戶或購買非書籍的 VIP 客戶。
  • 假定至少買 5 本書才能獲得免費送貨服務,那麼我們會得到以下預期:

Example:

客戶類型 購物車中的物品 送貨
VIP 5 本書 免費、標準
VIP 4 本書 標準
普通 10 本書 標準
VIP 5 台洗衣機 標準
VIP 5 本書、1台洗衣機 標準

這個需求說明 --- 一目了然的文檔 -- 可以用作實現的目標或自動化測試的驅動,這樣我們就可以客觀地衡量什麼時候算完成了。把它作為 LiveDocument 的一部分,保存在需求說明 Repository 中。FitNesse 的 wiki 系統或者 Cucumber 功能文件的目錄結構就是這樣的例子。

可執行的需求說明

當開發人員開始實現需求說明所描述的功能時,基於需求說明的測試開始時會失敗,因為測試還沒有自動化,功能也還沒有實現。

開發人員會實現相關功能並把它與自動化框架關聯在一起。他們使用自動化框架從需求說明中獲得輸入並驗證預期的輸出,而不需要實際修改需求說明文檔。當驗證實現自動化以後,需求說明就變成可執行的了。

Live Document

所有已實現功能的需求說明需要頻繁地進行驗證,一般通過自動化構建過程來實現。這樣可以確保需求說明保持更新,同時有助於避免功能退化的問題。

當實現了整個用戶故事的時候,需要有人去作首次驗證以確保其已經完成,然後重組需求說明確保它和已實現功能的需求說明是一致的。從需求說明逐步演化出文檔系統。舉例來說,他們可能將免費送貨的需求移到送貨相關的功能體系中,也可能將它們和其他因素促發的免費送貨實例合併在一起。為了更容易訪問文檔,他們可能會在免費送貨的需求說明和其他送貨類型的需求說明之間建立連結。

然後這個循環再次開始。一旦我們需要再次回顧免費送貨的規則 -- 比如,在做高級獎勵計畫,或是擴展功能把帶書籍的訂單和其他貨物訂單分離開的時候 -- 我們就可以使用 Live Document 來理解現有的功能並註明需要修改的地方。我們可以使用已有的實例來協作制定需求說明,同時舉例說明會更加有效。然後我們會舉出另一組關鍵實例,進一步演進免費送貨的需求說明,這部分最終會和需求說明的其他部分合併到一起。這個循環會不斷重複。

為何 BDD 沒有 TDD 那麼流行?

如果你身為 Rails Developer 又看到這套循環格式的話,你會馬上感到這跟一套測試框架很像,沒錯,就是 Cucumber

我第一次接觸到 BDD 的觀念大概是 2009 年。當然也是因為接觸到 Cucumber,才知道什麼叫做 BDD。但是一直以來,我能夠接受 TDD,但是 BDD 卻一直讓我無法理解。事實上,BDD 也一直沒有普遍流行起來

現在看完這本書,我才理解是什麼的盲點造成了實作上的心理障礙:在一般的專案開發中,通常業主不會要求開發者寫測試(甚至業主不理解什麼叫測試),所以通常測試是開發者自己寫的,為了正確構建功能,以及避免在專案後期踢到大鐵板,所寫的。

但是 BDD 的格式乍看之下卻相當突兀,以 Cucumber 為例,格式是這樣的:

  Scenario: Multiple Givens
    Given one thing
      And another thing
      And yet another thing
    When I open my eyes
    Then I see something
      But I don't see something else

敘述一個場景,然後寫下步驟,然後驗證步驟。這對一般開發者來說,BDD 相對多餘以及冗長。

原因何在?這是一般專案中,通常我們只會遇到兩種狀況:

  • 客戶疏於描述實作內容 : 只給解決方案,如必須要能夠進行付款。(但是卻沒有講清楚支援信用卡還是 ATM)

所以所謂 BDD 裡面的用戶故事,開發者必須要自行腦內補完。於是只要當「規格」一變,基於規格所生(想像)的整個用戶故事自然就會被摧毀。所以沒有人很喜歡寫這鬼東西。

  • 客戶過於精確的描述:對於描述操作步驟過於繁瑣,甚至是規定 UI (比如結帳必須要跳出一個 POP 視窗,等待信用卡驗證時必須要塞入一個等待過場動畫)

這又會變成另外一種情形,開發者把 BDD 當作是 UI 的驗收測試(尤其在使用 Cucumber 中特別容易被誤解)。在網站開發過程中,UI 很有可能是會變來變去的。沒有人會喜歡因為 UI 改變了然後又回去翻修用戶故事....

這就造成了為什麼人們寧願只進行 TDD,甚至只進行 Unit Test。因為比較不可能被需求變動整到。

但只進行 TDD 只能幫助我們:正確地開發一個產品。卻無法達到我們進行軟體開發最終的目標:「開發出一個正確的產品」。

Startup 前期應不應該導入 TDD / BDD?

在去年,我曾經寫過一篇 對 BDD / TDD 的看法,提到 Obie Fernandez 在 Rails Conf 2011 的 lightning talk 曾經給過這樣一個 lighting talk : Why BDD is Poison For Your Early Stage Startup 。並且在演講之後寫了一篇文章 The Dark Side Beckons?

Obie 的觀點正如 talk 名 "Why BDD is Poison For Your Early Stage Startup" 所言。他並且強調了:「Early on in the startup process, it's much more important to be testing against business metrics than anything having to do with code.」

「Until you are able to prove that you have a viable market, that customers will give you money for your product, you shouldn't be sinking a lot of time and money into implementation.」

現在回頭看起來,當時的討論完全是掉入方法論面的論述。我們以為 TDD / BDD 是「正確地開發一個產品」的一個手段。但這個手段會有相對高額的 technical cost。所以在 Startup 早期階段,開發者實際不應該投入過多心力在此之上。因為 Startup 的第一優先是「開發出一個正確的產品」。

而 Specification by example 卻強調的是,你應該透過這一系列的手段,利用 BDD 這樣的手法,摸索出一系列可以實作可以測試的正確軟體需求,從而交付出一個成功的軟體專案。

小結

如果你是抱著裡面有什麼厲害的大絕招,去翻這本書的話。我不敢保證你不會失望。因為這本書不太能算是一本嚴肅的方法論。裡面沒有 code,也不介紹任何工具。同時我也不推薦任何專案新手去翻閱這本書,因為這本書並不是什麼印度蛇藥,你一看完就會變成專案高手。

但是若你進行過不少專案,對於測試驅動開發、行為驅動開發、探索用戶需求有著自己的一番見解、疑問、心得。我相信這本書將會顛覆你的世界觀。

 
about 6 years ago

Paerclip.io

Hi, everyone. I want to introduce the new service I recently built at 2012 Facebook World Hack Taipei : 「Paperclip.io」. It is also the "Best Overall" service in Facebook World Hack Taipei.

What is Paperclip.io?

Paperclip.io tracks and collects webpages you liked via Facebook, and organizes them for you. You can browse or search through the liked pages quickly in Paperclip.io.

idea from …

The idea was from a small thing: I always forget what url I ever liked on Facebook. It was really annoying. So finally I decide to build a service to help me to collect and organize this url links.

Features

  • automatic tracking and backup
  • organized by various types, provides url info and snapshots.
  • can reshare to google plus+, twitter, del.icio.us…etc
  • searchable!!

demo video

video creditd by htchien

like us on Facebook:

https://www.facebook.com/paperclip.io

======

各位好。這是我昨天才剛推出的新服務「paperclip.io」。這個服務的主旨是要解決一個困擾:我們每天在 Facebook 上都會「讚」過很多網址。但是,就是因為「讚」過的東西太多了,每次要回去找今天或前幾天讚過什麼東西,都很麻煩。

所以最後我決定寫了一個服務來解這樣的困擾,它可以:

  • 每天自動備份你曾經「讚」過什麼
  • 按照 og:type 分類排好,而且有縮圖、大綱,
  • 還可以讓你容易再度的分享到其他服務(如 Google+, twitter)去。
  • 最棒的是可以搜尋!!也就是可以快速搜尋你曾經到底「讚」過什麼鬼了!

螢幕快照 2012-09-11 下午7.10.01

看了 demo 影片就知道!

這個服務也讓 我跟夥伴 zhusee 同時奪得了 2012 Facebook World Hack 台北站的首獎 (Best Overall)

歡迎各位試用!

如果使用上有什麼 bug 的話,請在這篇文章底下留言。我會儘快處理…

P.S. 因為機器小台,而且光今天用戶就瞬間爆增幾百個...,所以有可能你剛剛匯入的東西有可能不會那麼快跑出來,這目前不是 bug…還請見諒。

 
about 6 years ago

我 9/12 - 9/20 會在舊金山。參加 9/14, 9/15 的 Golden Gate Ruby Conf

如果有灣區的讀者想晚上吃個飯小酌,歡迎寫信給我 :D (xdite AT rocodev.com)

P.S. 我住 San Francisco 的 Mission District

 
about 6 years ago

這是前陣子在 LoneStarRubyConf 2012 上複習一些主題時,無意中翻到的一份宣言。

這份宣言我是從 Heroku 的工程師 Richard Schneeman 發表的這份 talk :Millions of Apps Deployed: What We've Learned 裡面翻到的。(不是很多人關注到。這份投影片我看到時才 100 Views 左右)

這篇投影片相當精彩,內容是從一份宣言 12factor.net 出發。列舉了十二項構建 Service as Service 所需的方法主題,再以如何使用 Rails 及其 Ecosystem 搭建出 Twelve-Factor App 宣言裡的需求條件為主旨展開。

因為這份投影片的內容,其實我在兩三年前就想寫過系列文章,但因為是一份相當宏大且沒有邊界(就當時來看)的主題,因此一直遲遲無法完成。社群內有人能夠整理出來,覺得實在太棒了。

不過我更好奇的是,若這能是一份非 Rails 為主軸的內容,影響層面應可以更大。念頭剛起,我就發現投影片的源頭出處 12factor.net 的這份宣言原本就是 language-agnostic based 的。

這麼宏大的主題,若是一個社群阿貓阿狗所寫,想必沒有說服力。但順著線索摸下去,我更發現 The Twelve-Factor App 這份宣言的起草人不是別人,正是 Heroku 的 Founder: Adam Wiggins。宣言的內容是他基於運營 Heroku 以來,公司經手過數十萬 Application 歸納出的結論。

這份宣言在前陣子已有人翻成簡體中文版 The Twelve-Factor App,我推薦各位絕對要去讀完...


(以下轉錄中文簡介,並對用語酌量修改)

簡介

如今,軟體通常會作為一種服務來交付,它們被稱為網路應用程式,或「軟體即服務」(SaaS)。「十二因子應用程式」(12-Factor App)為構建如下的SaaS應用提供了方法論:

使用標準化流程自動配置,從而使新的開發者花費最少的學習成本加入這個項目;
和操作系統之間盡可能的劃清界限,在各個系統中提供最大的可移植性;
適合部署在現代的雲計算平台,從而在服務器和系統管理方面節省資源;
將開發環境和生產環境的差異降至最低,並使用持續交付實施敏捷開發;
可以在工具、架構和開發流程不發生明顯變化的前提下實現擴展;
這套理論適用於任意語言和後端服務(資料庫、Message Queue、Cache等)開發的應用程式。

背景

本文的貢獻者者參與過數以百計的應用程式的開發和部署,並通過Heroku平台間接見證了數十萬應用程式的開發,運作以及擴展的過程。

本文綜合了我們關於SaaS應用幾乎所有的經驗和智慧,是開發此類應用的理想實踐標準,並特別關注於應用程式如何保持良性成長,開發者之間如何進行有效的代碼協作,以及如何避免軟件污染。

我們的初衷是分享在現代軟體開發過程中發現的一些系統性問題,並加深對這些問題的認識。我們提供了討論這些問題時所需的共享詞彙,同時使用相關術語給出一套針對這些問題的廣義解決方案。本文格式的靈感來自於 Martin Fowler的書籍:Patterns of Enterprise Application ArchitectureRefactoring

讀者應該是哪些人?

任何SaaS應用的開發人員。部署和管理此類應用的運維工程師。

The Twelve Factors
I. Codebase

One codebase tracked in revision control, many deploys

II. Dependencies

Explicitly declare and isolate dependencies

III. Config

Store config in the environment

IV. Backing Services

Treat backing services as attached resources

V. Build, release, run

Strictly separate build and run stages

VI. Processes

Execute the app as one or more stateless processes

VII. Port binding

Export services via port binding

VIII. Concurrency

Scale out via the process model

IX. Disposability

Maximize robustness with fast startup and graceful shutdown

X. Dev/prod parity

Keep development, staging, and production as similar as possible

XI. Logs

Treat logs as event streams

XII. Admin processes

Run admin/management tasks as one-off processes

 
about 6 years ago

Russian Dolls 097

大概半個月之前(2012/ 8/13 前後),@dhh 釋出了一個有關於 cache 的 gem,叫做 cache_digests,並宣布此 gem 會成為 Rails 4 中的一部分。

既然會是主體的一部分,想必這個 gem 解決的問題非常重要。但無奈 README 也非常簡略,看不出重要性在哪。還花了我一點時間在網路上找資料,把 DHH 想要表達的哲學拼出來....

從 new Basecamp 改版談起

@dhh 的公司 37signals 旗下最有名的產品 Basecamp 大概半年前改版了。與其說是改版,不如說是整個大重寫了。撇開使用性不談(好用度大幅提高),Website Performance 整體也大幅提升。

37signals 在大概二月發表了一篇文章,談了這次的版本為什麼效能變得這麼好:

1. Turn to JavaScript Applicaton

眾所皆知(?)的這次的改寫重點是在 JavaScript 上,整個 codebase 中CoffeScript 與 Ruby 的比例到達了 1:1 之譜。也就是 new Basecamp 實質上是個「JavaScript Application」。另外再利用 Stacker (advanced puhState-based engine for sheets) 大幅降低 HTTP requests。

2. Cache TO THE MAX: Russian Doll cache stratgy

雖然 new Basecamp 已經是個 「JavaScript Application」。但有個問題還是存在,身為 backend 的 Rails App 在 render Basecamp 的邏輯時,速度還是不夠快。於是他們採用「Russian Doll」的 Cache Strategy 把能 Cache 的部分擴展到上限…

Russian Doll cache strategy

如名所示,Russian Doll strategy,就是使用層層 cache 嵌套的策略。

這一張圖片的背後 code 會是這樣的寫法:

<% cache @project do %>
  aaa
  <% cache @todo do %>
    bbb
    <% cache @todolist do %>
      ccc
    <% end %>
  <% end %>
<% end %>

再利用 Rails 內建 cache helper 使用 "#{cache_key}/#{id}-#{timestamp}" (出處) 的方式去實作 cache invalidation。如此一來,只要 object 被變更,cache 就會被刷新。

但這招即使如此直觀,還是會遇到 invalidation 上的幾個問題。

1. 更新了 todolist,但是上層 class: todo 卻不知道…

todlist 更新了,所以 updated_at 會被更新。不過 todo 卻不知道 todolist 的更新,所以整塊並不會被更新。

解法:

不過解法容易。可以透過 belongs_to :todo, :touch => truebelongs_to :product, :touch => true。從最底部的 todolist,層層連鎖更新到最上層。

2. 更新了 code block,但 object 內容卻因為沒更新而不會 expire。

當我把 ccc 改成 zzz 時且打算 deploy 時,問題來了...。整套 cache 機制是基於 object 更新,而不是 code 更新。所以 cache 並不會 invalid….

<% cache @project do %>
  aaa
  <% cache @todo do %>
    bbb
    <% cache @todolist do %>
      zzz
    <% end %>
  <% end %>
<% end %>
解法:

這邊有另外一個寫法可以閃過這個地雷,就是為這整段 code 加上版本號:

<% cache [v15,@project] do %>
  aaa
  <% cache [v10,@todo] do %>
    bbb
    <% cache [v45,@todolist] do %>
      zzz
    <% end %>
  <% end %>
<% end %>

如果我要將 todolist block 那塊更新,要強制 invalid,我可以把 v45 改成 v46。這樣就更新了。

不過如果這一塊 view 上面還有外層 cache 嵌套,v10 要跟著變成 v11v15 要跟著變成 v16

有點麻煩了…

但這還不是最糟糕的…

3. cache 的部分散落在 partial 裡面,版本號更新不易

改版本號麻煩但還算可以接受。但這只限於都在同一張 view 裡面的狀況。

若是 cache 被放在 partial 裡面,被多個 view 引用呼叫,那就麻煩了…

_todolist.html.erb

<% cache [v45,@todolist] do %>
  zzz
<% end %>

改版本號的手續就變成地獄了…。因為你永遠都會有忘記清掉的 view…

解法:

暫無。認命的改吧(?)

4. 逐漸冗長的 syntax 問題..

而使用版本號閃避 cache 還會造成,原本直觀的

  <% cache @todolist do %>
    zzz
  <% end %>

為了要 invalid cache 的問題,被迫使用 trick 去 bypass。

  <% cache [v45,@todolist] do %>
    zzz
  <% end %>

可不可以單純一點,我們寫 code 還是回到直觀的 cache @object,然後以上談到的這些問題都會自動解決?

cache_digests 就是這一切的答案:md5_of_this_view

cache_digests 就是 DHH 解決這一切惱人問題的手段。

而且解決策略也非常簡單,既然大家都在版本號上面 GGYY,那麼其實最快的方式就是 md5_of_this_view !!!

cache_digests 允許開發者繼續使用這樣的 code:

  <% cache @todolist do %>
    zzz
  <% end %>

但!cache_digests 自動幫忙計算此 block 裡面的 code 產出的內容的 md5,以此 md5 作為 cache key,從而達到自動 invalid 的效果。

同時,這個 gem 也會自動解決層層嵌套的 dependency 問題…

小結

這一個 gem 前前後後不到 150 行。卻解決了一個非常重要的 cache 問題,也難怪會變成 Rails 4 之後內建的功能。

gem 雖然直觀。不過翻出這些前因後果還真是不簡單,在寫這篇文章的確也花了我花了一點時間去蒐集資料。從 37signals 釋出的一些小片段去把內容組出來。

相關連結:

 
about 6 years ago

這是我今天在 Ruby Tuesday #21 所寫的投影片。以下文章是寫投影片前已經擬好的草稿。可以配著服用

Rails Developer 在使用內建工具開發網站時,若是由小做起,通常不會踩到這些雷。但是若一開始開站資料就預計會有 10 萬筆、甚至 100 萬筆資料。執行 db 的 rake task 通常往往會令人痛苦不堪。

在這篇文章內,我整理了一些處理巨量資料必須注意的細節,應該可以有效解決大家常遇到的問題:

1. 盡量避免使用 ActiveRecord Object

ActiveRecord 當初的設計目的是為了框架內「商業使用」。它的工作是將純資料化為具有商業邏輯的 Ruby Object,並且配合框架,設計了多層 callbacks。

簡單來說,它並不是為了「處理 raw data」而設計。如果開發者只是要作一些簡單的資料操作,建議的方式請直接下 SQL,不要沾到任何 ActiveRecord。

(但大多數開發者直覺都是會開 ActiveRecord 下了條件就直接跑迴圈,忘記 MySQL 是可以直接拿來下指令的)

當然,沒有 ActiveRecord 這麼抽象化的工具,下純指令也是蠻痛苦的一件事,我推薦可以換用 sequel 這套工具試看看。

再者,在實務操作上我也建議避免使用 ActiveRecord + 內建的 rake task 操作巨量資料。原因是,開發者會順帶會把整套的 Rails 環境都載進來跑,其慢無比是正常的…

2. 有 update_all 可以用,少用 for / each。

通常會出問題的 code 是長這樣的:

posts = Post.where(:board_id => 5)

post.each do |post|
  post.board_id = 1
  post.save
end

這段 code 非常直觀,但會造成許多的問題。如果符合的條件有 10 萬筆,大概放著跑一天都跑不完....

先提供快速解法。Rails 提供了 update_all 可以下。可以改成這樣

Post.update_all({:board_id => 1}, {:board_id => 5})

基本上就是等於直接幫你下 update 的 SQL 啦。同樣資料量跑下去大概只要 10 秒秒以下左右吧。

3. 不要傻傻的直接 Post.all.each,可以用 find_in_batches

直接叫出所有符合的資料(Array) 是一件危險的事。如果符合條件的資料是 10 萬筆,全拉出來有高達 10G 的大小,嗯…我想機器沒個 10 G 以上的記憶體,指令下去機器直接跑到死掉有極大的可能性…

Rails 提供了 find_in_batches

Post.find_in_batches(:conditions => "board_id = 5", :batch_size => 1000) do |posts|
  posts.each do |post|
    post.board_id = 1
    post.save
  end
end

如果沒下 batch_size 預設一次是拉 2000 筆。可以一次指定小一點的數目,如一次 500 筆去跑。

4. 使用 transaction 跳過每次都要 BEGIN COMMIT 的過程,一次做完 1000 筆,然後再 COMMIT。

打開 Rails 的 development.log,這樣的 LOG 應該對你不陌生。

   (0.3ms)  BEGIN
   (0.5ms)  COMMIT

Rails 開發時,為了確保每比資料正確性,儲存的時候都會過一次 transaction,於是即使已經照 3 這樣的解法,還是要過 10 萬次 COMMIT BEGIN。很浪費時間。

Post.find_in_batches(:conditions => "board_id = 5", :batch_size => 1000) do |posts|
  Post.transaction do 
    posts.each do |post|
      post.board_id = 1
      post.save
    end
  end 
end

如果你只是要 update 少量欄位,而且資料處於離線狀態,可以使用 Transactions 搭配 find_in_batches,做完兩千筆再一次 commit,而不是每次做完就 commit 一次,在資料量很大的狀況下也可以節省不少時間。

5. 使用 update_column / sneacky-save 而非原生 save

用原生 save 會有什麼問題呢?原生的 save 在資料儲存時,會經過一堆 validatorcallbacks,即使你只是要簡單 update 一個欄位,會觸發到一狗票東西(假設 10 道 hook),那 10 萬筆就是過 100 萬道 hook 了啊。天啊 /_\,機器死掉不意外。

如果你想要閃掉 hook 的話,可以使用 update_column,

posts.each do |post|
  post.update_column(:board, 1)
  post.save
end

但 update_column 的缺點是一次只能 update 一個欄位,如果你有 update 多個欄位的需求,可以用sneacky-save 這套 gem。

如其名,sneacky-save 偷偷儲存不會勾動任何天雷地火。

6. 可以的話使用 Post.select("column 1, colum2").where

很多人會忽略一件事,Post.where("id < 10"),其實是把這 10 個 object 拉出 database。Post 裡面有什麼呢?會有幾千字的 content 啊。所以當你下了這道 comment 後,拉出來的是這些內容

Post Load (18.8ms)  SELECT `posts`.* FROM `posts` WHERE (id < 10)

拉出 10 萬筆會發生什麼事呢?(炸)

所以這也是我建議如果你沒有複雜操作(相依高度 model 邏輯)需要的話,千萬別碰 ActiveRecord,因為你不會知道會按下哪一顆核彈按鈕。

7. 使用 delegate 把大資料搬出去

ActiveRecord 裡面有 delegate 這個 API。如果你嫌要 Post.select("column 1, colum2").where 這樣東閃西閃很麻煩,還是希望使用 SELECT post.*。那麼不妨可以換一個思路,把肥的 column 丟到另外一個 table,再用 delegate 接起來。

class Post < ActiveRecord::Base
  has_one :meta
   
  after_create :create_meta
  
  deleage :content, :to => :meta
end

8. 操作資料前,別忘記打 INDEX

舉凡操作資料,多半是至少會先下個 condition。再看是直接用 SQL 處理掉還是跑迴圈。不過一般開發者最會中地雷的部分就是

  • 忘記打 index

忘記打 index 下 condition ,就會引發 table scan,這當然會很慢啊 /_\

  • 對 varchar(255) 直接打 index

使用 Rails 產生的 varchar,多半是 varchar(255),很少有人會直接去改長度的。而且使用 Rails 直接打的 index,也就是全長的 index 打下去了。效率爛到炸掉。

可以用這招 index 可以指定只取前面 n chars 的方式增進效率

ALTER TABLE post DROP INDEX PTitle, ADD INDEX(PTitle(13));

Percona 前幾天也有一個 talk 是 MySQL Indexing Best Practices,值得參考。

9. delete / destroy,刪除很昂貴。確保你知道自己在幹什麼。

首先第一件事要分清楚 delete 和 destroy 有什麼不同。

  • destroy 刪除資料並 go through callbacks
  • delete 刪除資料,不過任何 callbacks

所以要刪除資料前,請確認你用的是何種「刪除」。

destroy_all 和 delete_all 也是類似的原則。

找到符合特徵的紀錄,然後呼叫 destroy method。在這個動作中會引發 callbacks ….orz

找到符合特徵的紀錄,刪掉,但不觸發 callbacks

不過如果你真的要「清空 DB」。不要用 delete_all,MySQL 提供了:TRUNCATE 給你用。請用這個...

  • TRUNCATE TABLE
ActiveRecord::Base.connection.execute("TRUNCATE TABLE #{table_name}")

雖然 delete 不觸發 callbacks,但是「刪除」DELETE 真的很慢,因為
DELETE 涉及到會 update index,所以會…很慢。http://stackoverflow.com/questions/4020240/in-mysql-is-it-faster-to-delete-and-then-insert-or-is-it-faster-to-update-exist

如果你的資料要作大量的刪除動作,有兩種思路可以繞。

一個是使用軟性刪除 soft_delete,也就是加上標記標示已刪除,但實質上不從資料庫刪除資料,只 update 會比 delete 快一點。有 acts_as_archive 可以用。

另外一個想法是:與其用刪的 (DELETE) 不如用 塞的 (INSERT)

開一個新的 Table,用倒的,把 match 的 record 塞到新的 DB 去。INSERT 比 DELETE 快太多了。

10. 無法避免的昂貴操作丟到 background job 去操作。

使用 posts.each 是一個昂貴的線性操作。這個 process 會無限的膨脹及 block 資源。

所以可以改用一個作法,使用 background job 如

把昂貴的操作包成獨立事件。塞進 queue 裡面,丟到背景跑,然後開 10 支 worker,十箭其發,速度可以快不少。

之所以把 delayed_job 列出來又不推薦的原因是因為 delayed_job 清 queue 的方式是用 DELETE,在第九點我們談過了,在有大量資料的情況下,「刪除」這件事會非常昂貴。使用 delayed_job 無異是拿汽油澆火。

結論

十點列下來。我的建議是,如果你手上的資料量大到一個程度,能儘量回歸基本(SQL command)就回歸基本。因為使用 ActiveRecord ,開發者永遠不知道自己什麼時候會按下核爆彈的按鈕啊…

其他

目前我們固定在禮拜二,都會在 松江路的田中園 上舉辦 Taipei Rails Meetup。我自己本身也會固定在這裡免費幫大家解答 Rails 與 Web Operation 相關的問題。而坦白說,最近一些比較經典的 Post 也是從聚會裡的問答集裡面萃取出來的。

如果你對 Rails 有濃厚的興趣又住在台北,歡迎每週加入我們,謝謝!