什麼是 User Story?

User stories 是一種非常好用且容易上手的需求文件,它是一種極簡主義,只要求寫下最有價值不要忘記的東西,而且夠讓我們足以估計時程以及與客戶溝通。

嚴謹、漂亮、詳細的文件有兩大危險,一是製作厲害的文件本身變成了一個目標,而不是製作軟體。二是語言本身是模糊的,”詳細”的文件會造成”確定性的幻覺”,以為規格都確定了無需要溝通,最後做出根本不符合預期的軟體。敏捷開發鼓勵我們將時間多花在開發者、客戶、使用者之間頻繁的溝通,而不是製作文件。

什麼是 User Story?

User Story(使用者敘述)是一段簡單的功能敘述,以客戶或使用者的觀點撰寫下有價值的功能(functionality/feature)。與其說它是規格文件(documentation),不如說它代表(represent)客戶的一個需求而已,因為實做細節將延後至開發時才會確定。

幾個 User Stories 的範例如下:

  • 使用者可以在網站上張貼履歷
  • 使用者可以搜尋有哪些工作
  • 公司可以張貼新工作
  • 使用者可以限制誰可以看到他的履歷

初學的範本可以是:作為一個 (某個角色) 使用者,我可以做 (某個功能) 事情,如此可以有 (某個商業價值) 的好處。

As a (role of user), I want (some feature) so that (some business value).

閱讀全文〈什麼是 User Story?〉

Ruby on Rails Performance 最佳實務

這是我 2/26 在中研院 OSSF 工作坊和 3/23 在 Ruby Tuesday 所演講的題目之二。

關於 Performance,有四個最好不要做的事情:

  • 過早最佳化
  • 亂猜哪裡慢
  • 到處做快取
  • 與 Framework 過不去,硬要 Hack 它

然後有五條關於 Performance 的須知:

  • 演算法的改良永遠勝過硬擠程式碼
  • 一般來說,程式碼維護性的重要性勝過效能
  • 只最佳化最需要的部份 (80/20法則)
  • 測量再測量,最佳化前、最佳化後都要測量
  • 必須與程式碼彈性做平衡。有時候做了最佳化之後(例如快取),會犧牲掉一些彈性。

其他就看投影片吧。這份投影片主要還是關注在 Web application layer,也就是 Ruby 程式語言和 Ruby on Rails 上。有點小可惜的是篇幅和準備時間所限,關於 Front-end performance、NoSQL: Key-value stores 和 HTTP Caching 這三個主題是我覺得可以多講一點的東西。

有了效能,也許你會想進一步知道如何做 Scaling? 不同於 Performance 探討單一台 application server 可以服務多少 requests,Scaling 要探討的問題是如何擴展架構到多台伺服器上來服務更多流量。當然 Performance 會是做 Scaling 的一個重要的因素,不過 Scaling 需要考量的地方又更多了,包括:

  • Asynchrony Processing (Message queue)
  • Partition Component using SOA
  • HTTP Reverse Proxy Caching
  • Distributed Filesystem/Database

其中 Asynchrony Processing 和 SOA 的部份,我在 Distributes Ruby and Rails 的演講有分享過。

xdite 也有針對 Scaling Rails Site 這個主題寫了一系列的 Scaling Rails Site: Reading Material 來介紹: #1, #2, #3, #4, #5

Ruby on Rails Security 最佳實務

這是我 2/26 在中研院 OSSF 工作坊和 3/23 在 Ruby Tuesday 所演講的題目之一。

Ruby on Rails 要談的 Security 網站安全,主要在 Web application layer 的範圍(這也是最容易被攻擊的部分),像是 XSS、CSRF、Session Hijacking 跟 Fixation、SQL Injection 等等都是所有 Web 應用程式必須處理的問題。也有一些是 Rails 為了程式方便性而製造出來的問題,像是 Mass assignment、Unscoped finds、Controller Exposing methods 等等。

會講的內容看投影片就可以了,這裡我想特別提一下我的開場跟一個觀念:

我的開場引用了 PHP Security Guide: Overview 介紹什麼是 Security,我很喜歡:

  1. 安全性是相對的,不是一個功能。碰過太多客戶要求 “網站要絕對安全,不能被 HACK”,這實在太強人所難了。安全性就跟溫度一樣,相對熱、相對冷。安全性也是相對安全、相對不安全的,而不是像功能說有或沒有。
  2. 承上,要越安全,就給花越多成本去做。不過呢,不需要多花什麼錢,就可以很簡單做到足夠地安全。如果要非常非常安全,就會非常非常貴了。所以請考量你的預算。
  3. 必須要與使用性(usability)做平衡。很多時候,安全性跟使用性會是衝突的,想要越多安全性,就給犧牲掉一些使用上的方便性。
  4. 安全性必須是網站程式設計過程中的一部分,而不是最後才加上去。

而想提的寫程式觀念是我看 Security on Rails 一書裡面介紹的 Fail Close,底下第一段是用 Fail open 觀念寫的程式,第二段是用 Fail close。


# fail open way, it’s bad
def show
  @invoice = Invoice.find(params[:id])
  unless @user.validate_code( @invoice.code )
    redirect_to :action => 'not_authorized'
  end
end


# fail close way
def show
  @invoice = Invoice.find(params[:id])
  if @user.validate_code( @invoice.code )
    redirect_to :action => 'authorized
  else
    redirect_to :action => 'not_authorized'
   end
end

其中的端倪就是,在撰寫驗證安全性程式碼的時候,請用 Fail close 的觀念:”如果條件成功,才允許進行,不然就不允許”。而不是 Fail open:”如果條件不成功,才不允許,不然就允許。”。這個簡單的撰碼觀念,可以減少我們寫出漏洞程式碼的機會。

使用 A/B Testing 工具引導你做網站設計

當你有多個設計選擇,不知道哪一種比較好的時候,A/B testing 可以幫助你測試哪一種比較有效果。例如: 你的網站註冊有兩種設計方式,哪一種讓比較多人註冊呢? 你的購物車按鈕有兩種設計,哪一種比較讓人順利結帳呢? 你的廣告版本有三種,哪一種比較吸引人呢?

別猜了!! 公說公有理,婆說婆有理。讓使用者的實際體驗跟統計結果來告訴你吧。a/b testing 的作法是: 1.輪撥這些選項 2. 設定追蹤點(goal) 3. 一段時間後,觀察哪個選項達成的 goal 比較多。

而 A/B testing 工具可以幫助你很容易做好這些事情: 1. 針對不同人提供不同選項 2. 但讓同一個人看到的都是同一個選項(這是一個重點,不然測試就不準了),例如透過 cookie 或使用者ID 3. 提供後台報表,並提供告訴你這些數據是否有統計學上的顯著差異 4. 方便安裝及使用 5. 夠快,不會對 production site 造成效能負擔

對 Rails 來說,目前有兩套可以考慮使用: A/BingoVanity。A/Bingo 很容易安裝使用,功能比較陽春,使用 ActiveRecord 搭配快取(memcached) 來記錄資料。Vanity 功能比較豐富,願景也比較大,提倡了一整套的 Experiment Driven Development 開發方式。它使用了 Redis 來記錄資料。不過他的使用文件似乎沒有跟上程式的更新速度,所以不太好安裝,得去翻 source code。如果你現在就想試試,我會先推薦 A/Bingo 比較容易上手。

如果你不想改 server side 的程式(或是你是不會寫程式的行銷人員),也可以透過 Google Website Optimizer 這套工具來做 a/b testing。Google 的方式就單純用 JavaScript 來記錄: 你先告訴 Google 你有哪幾種頁面,例如 Original page、Variation page 1、Variation page 2 三種選項,接著提供 Conversion page 是指達成 goal 的頁面,然後將 Google 會給你 control script 貼到 Original page 去(讓使用者可以輪撥到其他頁面),以及 tracking script 貼到各個頁面。

最後,A/B Testing 當然也不是萬能的: Why A/B testing of web design fails

深入Rails3: ActiveSupport::Concern

(English version)

ActiveSupport::Concern 是 Rails3 做 Modularity 的一個重要的小工具。他的任務是讓管理 modules 之間的 dependencies 變得容易。

假設我們有兩個 Modules 有依存關係,module Bar 依存於 module Foo,然後有一個宿主 Host 類別希望 include Bar 的功能,我們可以這樣寫:


module Foo
   # self.included 這個函式會在 Foo 被 include 時執行
    def self.included(base)
        base.send(:do_host_something) # 對宿主做某些操作,例如增強功能等等
    end
end

module Bar
    def self.included(base)
        base.send(:do_host_something)
    end
end

class Host
  include Foo, Bar
end

這有個討厭的缺點就是,我們必須在宿主中同時 include Foo 跟 Bar,也就是要把所有依存的 modules 都 include 進來。這很糟糕啊,為什麼我們需要在 Host 裡面知道這些 modules 的依存關係呢 :/

我們希望能夠將 modules 的依存關係寫在 module 中,而宿主 Host 就只要使用就好了。所以我們試著改寫成:


module Bar
  include Foo # 因為 Bar 依存於 Foo,所以我們在這裡 include 它

  def self.included(base)
    base.send(:do_host_something)
  end

end

class Host
  include Bar # 只要 include Bar 就好,不需要知道 Bar 還依存哪些 modules
end

這樣乍看之下好像沒問題,但是卻有個嚴重的問題導致無法執行,因為 Foo 變成是由 Bar 所 include,所以對 Foo 的 self.included 來說,他的參數 base 變成了 Bar 了,所以他就沒辦法存取到宿主 Host 的任何函式及變數,do_host_something 時就會失敗。

Okay,ActiveSupport::Concern 就是來幫助解決這個難題,我們希望宿主可以不需要知道 modules 之間的 dependencies 關係。dependencies 關係寫在 module 裡面就好了。


require 'active_support/concern'

module Foo
    extend ActiveSupport::Concern
    included do
        self.send(:do_host_something)
    end
end

module Bar
    extend ActiveSupport::Concern
    include Foo # 因為 Bar 依存於 Foo,所以我們在這裡 include 它
    
    included do
        self.send(:do_host_something)
    end
end

class Host
  include Bar # 只要 include Bar 就好,不需要知道 Bar 還依存哪些 modules
end


如此就搞定了。One more thing,如果你有定義 module ClassMethods 和 module InstanceMethods 在裡面的話,它也會自動幫你載入到宿主裡面去,就不用自己寫 send(:include, InstanceMethods) 跟 send(:extend, ClassMethods) 了。用法舉例:



module Foo
    extend ActiveSupport::Concern
    included do
        self.send(:do_host_something)
    end

   module ClassMethods
      def bite
        # do something
      end
   end

   module InstanceMethods
      def poke
         # do something
      end
   end
end


想知道 ActiveSupport::Concern 到底怎麼實作的話,請看 /activesupport/lib/active_support/concern.rb,只有 29 行,而且 ActiveSupport::Concern 也沒有再依存其他東西了,嘿。

Rails3 如何換使用 jQuery

在 Rails3 中,處理 JavaScript 改採用 UJS 的方式,也就是以往 inline JS 的情況將不復見,link_to_remote 跟 remote_form_for 都沒有了:

例如:

link_to 'Logout', session_path, :method => :delete

產生出來的是:

<a href="/session" data-method="delete" rel="nofollow">Logoout</a>


例如

link_to 'Ajax delete', person_path(person), 
      :remote => true, :confirm => "Are you sure?"


產生出來的是

<a href="/people/1"  data-confirm="Are you sure?" data-method="delete" 
              data-remote="true" rel="nofollow">Ajax delete</a>  
# 點下去會向 Server 要求 JavaScript 內容執行

Rails3 預設還是使用 Prototype.js,要換成 jQuery 非常簡單。首先在產生 rails 專案時,可以輸入 rails your_project -J 就不會產生 Prototype.js 的檔案。

修改 /public/javascripts/rails.js 這個檔案,內容換成 github.com/rails/jquery-ujs/blob/master/src/rails.js 這個官方提供的 jQuery js driver。

在 Rails layout 的 HTML head 中加入:

<%= csrf_meta_tag %>
<%= javascript_include_tag "http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js" %>
<%= javascript_include_tag 'rails' %>

其中 csrf_mate_tag 產生的是做 CSRF 防禦的 authenticity_token,以往這也是 inline 在 HTML 中,現在則是先宣告在 HTML head 中。

另外要提醒你,如果你有使用 RJS 的話,Rails3 的 RJS 還是只會產生 prototype.js 的版本。所以如果非得用 RJS 的話,目前恐怕還是得裝以往 Rails2 用的 jRails,這好像有點蠢。也許是時候把 RJS 徹底揚棄,直接寫 jQuery 好了(?)

例如上面的 Ajax delete 例子,它的 controller 跟 js 可以這樣:

# people_controller.rb
  def destroy
    @person = Person.find(params[:id])
    @person.destroy

    respond_to do |format|
      format.html { redirect_to(people_url) }
      format.js # destroy.js.erb
    end
  end

# destroy.js.erb
jQuery("#<%= dom_id(@person)%>").remove();