16 May 2006

Ruby for rails 這本書剛剛入手,看了幾章之後,很想實際寫寫看。我找了個超迷你的 web app 來嘗試看看 - 一個訂便當的系統,含四個 table,四個網頁。

花了兩天,已經開發到堪用的程度了。對 Ruby on Rails 最大的感想是 -- Quick and Dirty

Rails 一開始就把整個開發的環境都設好給你了,而且有很多 script 可以省功。在 Java 界裡只有 Appfuse 有做這樣的整合。Rails 預設是用 MySQL,是我最討厭的資料庫之一。二話不說換成 postgreSQL,這當中也花了不少時間摸索... 如果撇開資料庫的設定不談,Rails 一開始馬上就能上工。

Rails 只有 MVC,Model/Controller/rhtml,所以也沒啥好分層的。我寫了幾個 action 後,發現程式邏輯不知不覺的會都集中在 Controller 裡。這種寫法很快,產生一個 view 和 controller 後,霹靂啪啦的便寫了起來,搭配可以直接看結果的網頁,三兩下就能寫完一個需求。不過就是 dirty 了一點。

code block 真是邪惡的功能... 有了這個很多東西用很短的 code 就寫完了

def buy
  order = Order.find(params[:order][:id]) 
        
  params[:item].each do |item_map|
    if item_map[1].empty? then next end
          
    item = order.shop.items.select{ |i| i.id == item_map[0].to_i }.first
    for i in 1..(item_map[1].to_i) 
       order.order_items << OrderItem.new do |order_item|
           order_item.buyer = params[:buyer].first
           order_item.price = item.price
           order_item.name = item.name
           order_item.payment = false
         end
    end
  end
end

上面的 method 共用了三個 block,如果把 for loop 改寫成 each 就變四個了。這玩意用過頭其實還蠻危險的。看這一行:

  item = order.shop.items.select{ |i| i.id == item_map[0].to_i }.first

這一行是在 order 相對應的店的產品中,找出第一個產品 id 符合 item_map[0].to_i() 的產品。哈... 中文好像越講越糊塗... orz。這樣囉哩吧嗦的邏輯 Ruby 用一行就能寫完了,當時我寫的也很快。可是事後想想這一行還真是恐怖,因為這已經大大的違反 Liskov Substitution Principle。比較安全的寫法該是:

  item = order.find_first_item(item_map[0].to_i)

不過這一個新 method 總共才少了 20 個字...,而且沒有 IDE 能幫你做 extract method... 所以... 後來我就沒去改了。

在來看看另一個 block,也是差不多的情形:

OrderItem.new do |order_item|
    order_item.buyer = params[:buyer].first
    order_item.price = item.price
    order_item.name = item.name
    order_item.payment = false
end

這是一個 construction 的 block... 裡面寫的是 constructor 的邏輯。喔~ 照我這樣寫,construction 的邏輯全部都 "露" 出來了。那要 constructor 幹嘛?封裝通通不見了...orz。當然你也可以好好的寫啦... 不過 block 太好用了,忍不住就給它 quick and dirty 一下,又不能 refactoring... 最後程式碼就是長這樣了。

我發現用 ruby 的習慣會變成不去做 refactoring (因為做不到...)。反過來,你會去想如何把程式碼寫的更短,最好是能把全部邏輯通通擠到一行裡。

用 ruby 寫程式比 java 快多了,不過常常要查文件,或查 DB schema,因為 api 的名字、參數、回傳變數在開發時通通看不到。而 ruby 的 method 又很多 (因為崇尚 HumaneInterface)。變成你需要額外花精力去背這些 API。這不見得是一件壞事,舉例來說 -- 有人會去買全部黑色,沒有刷字母的鍵盤,用了幾個禮拜後,打字速度馬上倍增。我想 ruby 這個缺點在使用幾週之後也會消失,反到是寫程式的速度會加快許多,因為潛力反被激發出來了。

Rails 的 rhtml 真是... 他媽的醜:

<%= start_form_tag :controller => "shop", :action => "create" %>
      name: <%= text_field "shop", "name", :size => 15 %>
      desc: <%= text_field "shop", "description", :size => 15 %>
       tel: <%= text_field "shop", "tel_no", :size => 15 %>
            <%= submit_tag("create") %>
<%= end_form_tag %>

看了只有 orz... 不要說跟 Wicket 比,Struts 都比他好看多了...。如果有人能寫出個 Ruby 版的 Wicket,那真是天下無敵啦!

Rails 也有 boiler plate code,就是最討厭的 transaction:

def modify
    Shop.transaction do
      shop = Shop.find(params[:shop][:id])
      shop.update_attributes(params[:shop])      
    end
end

Shop.transaction do ... end 這段碼真是太醜了,而且寫的到處都是,這讓我回憶起以前用 HibernateUtil 痛苦時代。Spring 的 AOP transaction 好用多了。現在還不曉得怎麼在 Ruby 裡用類似 AOP 的功能 (應該比 java 簡單) 來改進這個問題。

Ruby on Rails 是不能在企業裡當正式的平台啦... 但是它非常非常適合用來做 prototype。prototype 本來就不用太計較程式碼嚴不嚴謹,transaction/thread 完不完善,效率好不好,只要能動就行了。如果要寫 prototype,我想沒人比 rails 更好。

現在 "訂便當系統" 還剩 validation 沒寫,正在苦鬥中... 試了老半天一個 message 都生不出來,快要放棄了... >_<

ps: 以上是沒寫 unit test 狀況下的想法,若用 TDD 來開發 Rails web app 應該會差很多吧....


回響

可以用 Tag <I>、<B>,程式碼請用 <PRE>