Essentail Rails Design Pattern

Write Good Rails Code

View: Helper

Helper 與 Partial 的運用是幾個初學者使得不稱手的主題之一。原因有幾:

  1. 不知道有 Rails 提供了許多好用的 Helper 可以用
  2. 不知道 Helper 與 Partial 他們各自的使用時機。
  3. 擔心使用 Helper 會造成效能下降。
  4. 以不好的方式使用 Helper 反而使維護性降低。

Helper 與 Partial 的不同

Partial

Partial 指的是局部樣板。而 Helper 指的卻是在樣板中的一些輔助方法(Ruby Method)。這兩種都是整理又臭又長的 HTML 版面時的好工具。

一般而言,我們會使用 Partial 去處理大段且重複的程式碼。或者是經常使用到的局部程式碼。

  • 大段程式碼:(如 new / edit 會複用到的表單)
1
2
3
<%= form_for @post do |f| %>
    <%= render :partial => "form", :locals => { :f => f} %>
<% end %>
  • 經常使用到的局部程式碼:(如 sidebar 內的區塊)
1
2
3
4
<div id="sidebar">
  <%= render :partial => "recent_posts" %>
  <%= render :partial => "recent_comments" %>
</div>

…etc.

Partial 的優點

  • Don’t repeat yourself(DRY)程式碼不重複
  • 程式修改會比較清楚
  • Partial 樣板比較容易被重複使用

Helper

Partial 的定位多半是被用來處理「大片 HTML 」的工具,而 Helper 卻是比較屬於需要邏輯性輸出 HTML 時用的整理工具。

一般我們學 Rails 常見的

  • stylsheet_link_tag
  • link_to
  • image_tag
  • form_for 中的 f.text_field…etc

都屬於 Helper 的範疇。

在專案中使用 Rails 內建 Helper 開發的原因

其一:為了省力

Rails 最令其他 Ruby Web Framework 羨慕的,就是內建的很多方便 Helper。

舉幾個其實很方便,但大家其實不太知道它們存在的 Helper 好了。

  • simple_format : 可以處理使用者的內容中 \r\n 自動轉 br 和 p 的工作
  • auto_link :可以處理使用者的內容中,若有連結,就自動 link 的工作。
  • truncate: 使用者輸入的內容,若過長,可以指定多少字後就自動砍掉,並加入 “….”
  • html_escape: 使用者輸入的內容,若有 html tag,為了怕使用者輸入惡意 tag 進行 hack。自動過濾。(以前要手動加 h 過濾,現在 Rails 預設 escape,不想被 escape 才手動指定 raw 閃掉 escape)

這些東西若自己寫 parser 處理,不知道要花費多少精力,還不一定濾的徹底。卻都是 Rails 預設內建 Helper。

其二:為了跟 Rails 內建的其他更棒的基礎建設整合

  • stylesheet_link_tagimage_tag

有些人也覺得,這東西還要用 Helper 嗎?直接貼 HTML 不是也一樣會動嗎?有什麼差別。差別可大了。

1
 <%= stylesheet_link_tag "abc", "def", :cache => true %>

這一個 Helper,可以在 production 環境時,自動幫你將兩支 CSS 自動壓縮成一支 all.css 。直接實現了 Yahoo Best Practices for Speeding Up Your Web Site中,minify HTTP reqesut 的建議。而在 Rails 3.1 之後,甚至還會自動幫你 trim 與 gzip。

完全不需要去在 deploy process 中 hook 另外的 compressor 就可以達到。

至於 image_tag 有什麼特別的地方?

1
 <%= image_tag("xxx.jpg") %>

Rails 可以幫你的 asset 自動在後面上 query string,如:xxx.jpg?12345

這樣在網站若有整合 CDN 架構時,可以自動處理 invalid cache 的問題。

而 Rails 也有選項可以實作 asset parallel download 的機制,一旦打開,站上的 asset 也會配合你的設定,亂數吐不同來源的 asset host 實做平行下載。

輕鬆就可以把網站 Scale 上去。

  • form_for

這也是 Rails 相當為人稱讚的一個利器。Rails 的表單欄位是綁 model (db 欄位的),除了開發方便( Post.new(params[:post]) 直接收參數做 mapping)之外,也內建了防 CSRF (protect_from_forgery)的防禦措施。

開發者可以在一眨眼的工夫,借助框架的力量,實作出業界(performace /security)最佳實踐。而卻不需要先修煉成架構大師。

為什麼在專案中我們要撰寫「自用」 Helper?

其一:在 View 裡面實作 LOGIC 是不好的。

造成效能低落

在 View 裡面實作 LOGIC 的影響,最主要導致的後遺症就是:「效能低落」(此部份詳細來龍去賣在其他章節會進行解釋 #TODO)。簡單版本的原因是 ERB 是用 eval 實作 執行 Ruby code 的,在 View 裡面穿插大量的 LOGIC 會造成 render 的效率低落。

造成程式碼混亂難讀

View 在 MVC 的模式中,原本就是只為了做 UI 輸出的功用的。如果有程式邏輯,或者是資料查詢,應該挪到 Controller 或 Model 去做。

這通常在 PHP 轉檯轉過來的 developer 身上,比較能找到這樣的問題。原因是在 PHP 程式寫作開發中,這是很天經地義的作法。但眾所週知的,PHP 的 project 也特別容易藏汙納垢。

如果你手上的 Project,View 打開來都 7-8 頁以上,別懷疑,肯定都是 LOGIC in View 造成的。

而根據過往經驗:有冗長 View 的 project,往往比有冗長 controller 的 project 還要難纏。

一個 View 裡面若有著很多 if / else / elsif , query_some_data。或者有著 if / else / elsif , change some css class ?

人的大腦不是 Ruby Interpreter,很難腦內想像這麼複雜的 code 會長什麼樣子,沒多久就會當機的….

其二:讓 View 更加直觀好維護。

在沒有注釋的情況下,你看得懂這段 code 的意圖嗎?

1
2
3
  <% if current_user && (post.user == current_user || current.user.is_forum_admin? || current.user_is_admin? ) %>
  <%= link_to("Edit", edit_post_path(post) ) %>
  <% end %>

這行範例程式碼,我想邏輯還算簡單,應該不難讓人讀懂。不過若再經過兩三輪的維護,應該就會變得超難維護了。

正確的整理法

正確的整理法其實應該要把邏輯拆出來,放在 Helper 裡。就像這樣:

1
2
3
  <% if can_edit?(post) %>
     <%= link_to("Edit", edit_post_path(post) ) %>
  <% end %>

這樣就能讓後續維護者知道,這一段程式碼就是在表示:如果當前使用者有權限編輯,就應該顯示編輯頁面的連結。

其三:給 Code 取名字,容易尋找並複用成果。

前段程式碼寫得不算太優,因為到目前為止它只表明了:如果當前使用者有權限編輯,就應該顯示編輯頁面的連結。。並沒達到正確闡述自己存在的的意義。

而這一塊原始碼的意圖是:若有編輯權限,就應該顯示一塊 toolbar。

1
<%= render_tool_bar %>

包裝成 toolbar 後,這一塊程式碼就變得有名字了。下次開發者在某個頁面要寫到類似的功能時,就只要呼叫 render_tool_bar 就可以了。

而最重要的,是以後再維護這一塊程式碼時,完全不必再猜測程式意圖,也很容易找當初亂丟在哪裡了。

其四: 不會被以前寫的 Code 害死。

1
2
3
  <% if current_user && (post.user == current_user || current.user.is_forum_admin? || current.user_is_admin? ) %>
  <%= link_to("Edit", edit_post_path(post) ) %>
  <% end %>

這種 code 在網站初期建設需要高速開發時沒有什麼不好。其實也還算讓人看得懂…

但我個人的建議是:一旦接近完工狀態,就要儘快抽空 refactor 掉它。

沒有組織結構的 code 一旦越來越多,專案就會越來越難維護。累積到一個程度,就會轉變成 unmaintainable 的狀態,到時候就算呼叫神仙也難回天。

像是我個人參與的專案,在開發中都有一個慣例:一旦專案開發快到尾聲,大家就會開始整理程式碼,把重複的部分重新翻修包成有名字的 Helper。

這樣作有什麼好處?

  1. 網站以後要進行結構重整時,只要調整已定義好的 Helper 內部架構就好了。如果還是東一個西一個到處亂放,同樣的東西重複貼 10 個地方,將來想要調整就要改 10 遍。相信我,你不喜歡在「改版時期」改 code 改 10 遍。就算是可以使用 git grep 抓漏還是會失手…

  2. 無痛升級 Rails 版本。有很多人好奇筆者手上的每個專案,為何可以一路從 2.3.x 一路升升升到 3.1.x 去。卻還算輕鬆愉快?

印象當中這中間的改版:不是有令人抓狂的 html_escape API 行為改變問題?asset_pipeline 架構調整?這些都是很令人抓狂的過程。不少開發者都在升級過程中放棄了。為什麼我們辦得到。

我想,當中最關鍵的原因,不在於「有沒有寫 test」,而是在於「有沒有擁有定期整理 code 的習慣」。

若定時整理 helper / partial / controller / model 的程式碼。只要是發現重複 code,馬上進行封裝整理。將來就算有東西爆炸,多半也只要調整一下 helper 或者是 model 的輸出,馬上就解決問題了。

所以就算是遇到 Rails 升級的問題,開發者也可以專注把精力集中在處理幾個 deprecated method 或 incompatible API 的調整。就算 view 爆掉了,修一個地方,十個地方隨之生效。自然寫意無比。

其五:容易複用並開創專案打下來的 best practices

在進行專案過程中,也會漸漸的養出自己的一套 HTML 架構 與 CSS (SCSS)。很多元件在不同專案中都是共通的,比如說自己用來 bootstrap (非 twitter)專案的 view 和 helper。

navgation_bar, user_bar, breadcrumb, menu list, table, button,gravator 這些都是專案必備。

這是台灣知名的 Rails 網站設計公司 Handlino 設計的一套 helper https://github.com/handlino/handicraft_helper

可以很方便就寫出 menu, table, body class with browser type, breadcrumb…etc.

而筆者在自己作網站還會多裝上幾套自己養出來的標準 Helper

  • SEO 最佳化實踐
  • Social Media Share-Friendly
  • Content Site 常用功能最佳實踐

專案越寫越多,後期越來越輕鬆,但不管之後再寫什麼新網站,依舊是幾乎都預設含有以前維護舊網站時打下的 Best Practices.

這就是越資深的 Rails Developer 開發新專案能夠越來越快速的原因之一。

Comments