ADZ 學習筆記

Ruby/Rails, Startup, Life

2014年回顧和明年期許

| Comments

兩年前開始創業時,我知道我自己不能只專注在技術上,雖然有這樣的 idea 但當時我並不知道可以做什麼。經過一年的嘗試我慢慢找到一些事情的樂趣和價值,這篇文章想分享 2014 年完成的事和明年的期許:

2014達成

  • 辦了 50 場 Rails Meetup (平均人數8-15人) + 2 場 workshop
  • 四次分享 (交點, KSDG, Rails Meetup/Talk)
  • 寫了 46 篇文章 (去年 11 篇)
  • 完成了 7 個新專案 (3,513 commits)
  • 釋出四個 opensource tools/gems 並練習寫英文文件

今年參與了更多社群活動。在工作上、或是解決別人問題的過程中,也開始養成習慣花一點時間梳理想法並記錄下來,寫文章的效率比以前高了大概3倍。下半年則開始養用英文寫 document、commits、comments、tickets 習慣。

2015目標

  • 辦 12 場 Rails Talk
  • 20次以上技術/非技術分享
  • 50篇以上英文文章
  • 參與更多非技術性聚會
  • 減少寫程式時間,把 work smart 做到極致
  • 把 agile 練好
  • 150小時英文 conversation
  • 學會自製炸蝦

有效率學習一門技術的訣竅 - 突破靠想像力

| Comments

這裏說的想像力一種把某件事想成某個樣子的能力,在解決問題上扮演著「如何簡化、或複雜化問題」的關鍵,而在學習上也影響著效率、和理解抽象的知識的能力。

學習歸零

曾經有一個程式設計題目如下:

# input:
3

# ouput:

1 1 1 1 1
1 2 2 2 1
1 2 3 2 1
1 2 2 2 1
1 1 1 1 1

一開始解題的思路是,我要想辦法先從 input 3 得到寬高 5,再用兩層回圈去處理 offset。硬幹出第一個版本後一直在想有沒有辦法讓 offset 的處理變簡單。

過了幾個禮拜後想到一種方式來實現,就是「把這個圖案想成金字塔、數字代表高度、樓層」用這概念來想,不用處理那麼多 offset,概念上也變得簡單多了,實作上只需要三層迴圈:一層迴圈跑樓層 (1..3)、第二層跑樓層的寬、第三層跑樓層的高,過程用 override 的方式 蓋上去

雖然說第二個版本的效能會比較差,但如果面對的問題是複雜到無法寫出第一種答案的情況,第二個版本的問題理解方式就派上用場了。

這也能說明為什麼有些人寫程式一直很順利,有些人會一直碰到一些奇怪的問題,有時候我們常常會卡在一個觀點,其實放棄它只需要一個念頭,從0開始重新認識問題。

把抽象概念朔型

我還記得好幾年前聽一些前輩在談論怎麼設計系統,內容充斥著專有名詞、抽象概念,那時的我聽起來很吃力,也很好奇他們真的都懂對方在說什麼嗎?還是只是敷衍再隨便聊聊。不過近幾年我也變成那樣子的人了,其中最大的改變是,我對那些抽象事物的了解已經在腦中有非常清楚的畫面。

當我能夠想像越多抽象的東西例如:layer, queue, chain ... etc,有種被打通任督二脈的感覺,我開始能理解 framework 或 ORM 怎麼被設計、也開始懂得欣賞別人的程式碼,直到最近這一年我才開始覺得我真正開始做程式 設計 了。

小訣竅

剛剛說的想像力不是天馬行空式的,你可以透過 更小的學習範圍更大量練習 去體驗你正在學的東西,過程中你可以試著把學到的東西跟你以前學過的東西、或現實生活中的經驗關連起來,並用很多不同觀點去看同一件事情。

系列文章

  1. 有效率學習一門技術的訣竅 - 閱讀:練習 (1:9)
  2. 有效率學習一門技術的訣竅 - 讓需求引導學習
  3. 有效率學習一門技術的訣竅 - 避開學習障礙
  4. 有效率學習一門技術的訣竅 - 當個容易被幫助的人
  5. 有效率學習一門技術的訣竅 - 突破靠想像力

有效率學習一門技術的訣竅 - 當個容易被幫助的人

| Comments

昨天看了一篇關於 語言癌 的文章,還滿有趣的,這或許反映了現在網路越來越即時所造成的問題:「你只要打幾個字按下 Enter 對方就會馬上收到訊息,缺少思考的文字混雜著情緒、無用的資訊、和混亂的邏輯」。

10 幾年前的網路環境,沒有 Facebook、沒有 meetup、沒有完整的技術文件、沒有即時討論程式的地方,只有幾個寫程式的論壇網站,討論的人也相當分散,在那個時候上論壇問一個問題至少要等 4-8 小時甚至隔天才有人回覆,有時還會音訊全無。

在這樣的環境下,來回一次的溝通成本非常高,如果我沒有想盡辦法把問題一次描述清楚,我就要再等 4-8 小時才能再次得到別人的回覆,更別說抓不到重點的問題,可能花一個星期也得不到答案。

我很幸運我經歷過那個網路緩慢的年代,逼著我養成習慣在向別人請教問題前先打開記事本把問題歸納、釐清才送出。直到現在我依然花很多時間去描述我的問題、確保沒有缺少資訊、和清楚的問題核心。

如何問問題

1. 確保資訊完整

記得曾經有個人在 facebook 問 rails deploy 的問題,以下是模擬對話:

A: 問一下喔,我的 rails app 不能 deploy
我: ?
A: deploy 到一半出現錯誤
我: 什麼錯誤?
A: 就一個 exception 什麼的,你有遇過嗎?
我: 你要不要貼 error message 給我看一下
A: 可是我是照 XXX 的文章照做,結果跑到一半就一直出錯
....
....
....

像這樣缺少資訊的問法加上用聊天室的方式詢問,其實就是把自己和對方的時間綁住一起浪費掉,而且不會有任何進展。

如果你的問題包含很多資訊 程式碼錯誤訊息你的思考方向 等等,最好開先開份 evernote 在上面把資訊打完整。最後直接把 link 貼給對方,送出前記得把多餘的資訊、贅詞刪掉後再送出。這樣一來除了可以訓練自己的文字表達能力外,還能讓對方依照自己的時間被動處理、用自己的習慣來消化問題。

2. 尚未歸納的問題不是問題

如果依照 上一篇 的做法,會先有個基本的歸納,把問題拆成很多個小 part,問的問題相對明確好回答。而歸納過後的問題才算是真正的問題,不然其實只是想把責任丟給對方而已。像是有的人會直接問:「購物車怎麼做?」這個問題很模糊也非常多層面,是想了解概念? 還是流程? 還是 schema design? 還是實作哪個部分遇到困難? 每次遇到這種問題我都有種在 try 別人網站的感覺 ... oh 終於找到 injection 點了。

讓別人幫你歸納問題的壞處是,你只是吃到魚、釣竿還是別人的,但學會怎麼歸納後等於是發展出一個比釣竿更有效率的工具。

3. 描述自己的觀點

忘記在哪個地方看過一段話:「問對問題比回答更重要,但問對問題相對困難」這句話我非常認同,越簡單觀點,也會得到越簡單的解法,越複雜的觀點,得到的解法也越複雜。

在問問題中如果能提供更多資訊關於你的想法:為什麼這樣設計? 為什麼使用這個工具? 為什麼採用這個演算法等等、甚至是為什麼要問這個問題? 這些資訊有助於讓別人瞭解你的目的跟思考路徑。

「如果你問別人怎麼養牛,卻不透露你的目的只是要喝牛奶」別人是無法告訴你對「喝牛奶來說、如何養牛不算是個問題

4. 小技巧

使用工具

evernote 只拿來描述問題、搭配 gist 或其他服務外連程式碼、錯誤訊息、圖片等等。

多個問題時

同一個情境下如果有多個問題可以標註 Q1, Q2, Q3,這種描述方式會更明確、訊息往來也會有得對照。

把資訊分類

被請教的對象通常是較為 senior 的人,在了解事情通常會有自己的方式,絕對不會照單全收,而是藉由推理選擇性吸收資訊增加效率,所以最好可以提供 200 字內的摘要描述問題或情境、並分段落標題讓對方容易依照自己的習慣消化問題。

系列文章

  1. 有效率學習一門技術的訣竅 - 閱讀:練習 (1:9)
  2. 有效率學習一門技術的訣竅 - 讓需求引導學習
  3. 有效率學習一門技術的訣竅 - 避開學習障礙
  4. 有效率學習一門技術的訣竅 - 當個容易被幫助的人
  5. 有效率學習一門技術的訣竅 - 突破靠想像力

有效率學習一門技術的訣竅 - 避開學習障礙

| Comments

知道了練習比知識的重要、和讓需求引導學習之後,再來的就是一些小技巧能夠讓你在學習的過程中減少挫折感。

設定學習範圍

通常跑馬拉松放棄失敗的人都是一直盯著目標,最後被自己的挫折感打敗的,而能夠堅持到終點的人往往都是幫自己重複設定短又容易達成的目標。對我身邊想學好 rails 的人,我都建議他們直接開始挑個小項目開始動手。小項目指的不一定是專案,而是小到不要讓一堆你不熟悉的東西 同時 混入阻礙你的練習。

訂好一個小項目後,這個東西對你而言可能還是有點模糊的階段,所以還需要縮成多個更小項目來練習、增加經驗上的知識。

有一點概念的新手

好像知道怎麼做、又很模糊,說直接一點其實就是不會,會停留在這個時期的原因就是缺少大量實作,這時候可以幫自己設計一些情境 (學習範圍) 讓自己大量去接觸某個 rails 的功能,例如:「商品管理功能」,並拆成以下項目:

  1. 做一個 product 的 CRUD
  2. 在 create/update 時檢查 product 的 title, description 必填
  3. 在 create/update 時檢查 product 的 qty, price 必須 > 0
  4. 清單頁填入 title 模糊搜尋出 product title
  5. 清單頁有 tab 可以顯示上架中、下架中、已刪除選項

對原本不熟悉 SQL 和 db schema design 的人來說,做完這五條需求可能會用掉超級多時間,不過千萬不要因為這樣就氣餒,會慢是因為同時在學習好幾個不同的技術。

如果真的感到很挫折,可以去社群尋求協助。問前輩:「我在 db 要怎麼儲存產品上架/下架/刪除的狀態」,絕對好過問:「購物車要怎麼做?」還要實際、別人也比較願意回答。換言之,如果別人不願意回答你,或無法一次回答你,很有可能是你設定的項目還是太大以至於問題太模糊分散。

最後花了很多時間把這五個項目用最低標準完成後,至少可以學到 model 的 scope, validation 怎麼使用,就離開了 有一點點概念 的階段了。

心理建設

另外 把大項目切細 這件事情也是需要學習,它是一種歸納的過程,會讓 執行路徑問題 就浮現出來,你也會比較清楚知道怎麼問問題,問題的關鍵點在哪? 所以千萬不要誤以為這樣可以輕易 解決問題,它只是個讓 問題突顯 的手段。

既然把項目切細是一項需要學習的事情,我想幫大家做一樣心理建設就是,這不容易,而且就跟學習其他技術一樣,需要改變習慣。對熟悉的人來說也需要花時間,這就是計畫的成本。所以千萬不要因為卡在這裡很久就放棄,這裏花的時間絕對會在後面賺回來。

系列文章

  1. 有效率學習一門技術的訣竅 - 閱讀:練習 (1:9)
  2. 有效率學習一門技術的訣竅 - 讓需求引導學習
  3. 有效率學習一門技術的訣竅 - 避開學習障礙
  4. 有效率學習一門技術的訣竅 - 當個容易被幫助的人
  5. 有效率學習一門技術的訣竅 - 突破靠想像力

有效率學習一門技術的訣竅 - 讓需求引導學習

| Comments

繼上篇 閱讀:練習 1:9 後,如果大家能認同練習的重要性,接下來遇到的就是怎麼安排學習路徑的問題。

從需求到技術

我們教育最大的問題在於分科制度,在還不知道為什麼的情況下學了國文、數學、計算機概論。這種教育方式設計的目標是希望這些知識最後拼湊起來可以組合出不同領域的人才。這種方式的弊病來自於上篇提到的想法:「這個時代我們更缺乏 能夠掌握的知識 而不是停留在 level 1 的理解」。

所以更好的方式是拋棄以往分科的方式,以需求帶領學習。開始問自己最想做什麼? 想自己寫一個 blog? EC平台? 為解決某某某問題而存在的軟體?

一旦有了需求,心中自然就會產生這個產品的畫面,然後很快就會遇到第一個問題:「我要怎麼在網頁上呈現我要的畫面」,從這個起點開始不要多也不要少的去吸收知識,然後實作。

從需求出發到最後完成一個成品,你會發現做一個簡單的網站會用到各個領域一點點的知識,在過程中得到的成就感也能夠抵銷你的挫折感,讓你能夠持之以恆,甚至最後的成品自己還用得上。

我的另一個觀察是,很多 課本的信眾們 不理解現在常用的架構和 pattern 都是從真實需求經過非常多的取捨演變來的,那只是個基礎模型,不是絕對也無法套用在各種情境。通常這些東西是有非常大的 討論空間,而討論到最後提出解決問題的過程才是真正價值的所在,經歷這段從需求到解決的過程中,等於是你參與了設計、跟所有取捨,價值不只是技術上而已。

氣宗還是劍宗?

起初我認為軟體設計的基礎功非常重要,於是我在 kaohsiung rails meetup 試著教新手資料型態、物件導向 (class, abstract class, instance, method, variables, property, interface, inherit) 和 http protocol/header 瀏覽器怎麼和伺服器溝通、cookie 怎麼建立識別身份、可能存在的風險有什麼 ... etc,但這種教學方式試到最後發現效果非常差。

回頭想想,我當時也不是先打好基礎才開始寫程式,而是需要什麼學什麼,但我現在卻反過來覺得應該要先懂基礎才能開始寫程式好像不太對。我想這個過程是:「當走過基礎學習期會開始覺得 基礎超重要,所以想建議新手打好基礎,但對新手來說,從無趣的基礎學習起實在太難了太容易放棄了」。

所以個人認為最好的方式還是從 需求實作、最後回到 基礎

系列文章

  1. 有效率學習一門技術的訣竅 - 閱讀:練習 (1:9)
  2. 有效率學習一門技術的訣竅 - 讓需求引導學習
  3. 有效率學習一門技術的訣竅 - 避開學習障礙
  4. 有效率學習一門技術的訣竅 - 當個容易被幫助的人
  5. 有效率學習一門技術的訣竅 - 突破靠想像力

有效率學習一門技術的訣竅 - 閱讀:練習 (1:9)

| Comments

學習最終目的就是要 用得上,但我觀察到一些人對於學習好像有點誤解以至於讀了很多書、花了很多時間,但始終無法真正應用,幾個月前我在交點分享關於教育的主題就是在談論:如何自主學、有效率的學習。

閱讀:練習 (1:9)

學習分為三個階段,拿牛肉麵來做比喻,如果你聽說某家牛肉麵很好吃,這屬於第一個階段。當你看到那碗牛肉麵、也聞到了,那屬於第二個階段。最後你真正吃到那碗牛肉麵,屬於第三個階段。這些階段就是從知識到經驗的過程。

依照我的觀察,這個時代知識、資訊非常氾濫,我們最不缺的就是吸收新知識,而是建立經驗,所以我自己的學習方式秉持著花一小時閱讀、用十小時去使用,建立經驗。

以 ruby on rails 為例,當你知道 scaffold 以後,想辦法找情境去應用他:

  • 程式新手的話,應該去看看其他網站,去想 scaffold 可以拿來做什麼? 嘗試把自己想像的功能做出來,即使 bug 一堆也沒關係。
  • 已經熟悉其他語言的人,應該把以前的經驗對應到新觀念,不斷問自己,以前我要做一個 XXX feature 現在用 scaffold 怎麼做? 並實作出來,只有透過這個過程,才能真正了解 rails 的特點,而不是只有讀讀 document 的程度。

BTW: 大家都知道 rails 實現商業點子速度很快,但真正已經在寫的人才會知道 Ruby 語言特性上也佔了很大一部分原因,這是只讀讀 document 無法了解在經驗等級上的知識。

另外學習如果只在知識層面來堆疊,是不穩固的,而且很沒有效率,這個轉化為經驗的過程有點像是把你腦子裡的軟體邏輯、變成硬體電路,用這種方式得到的知識最後像是你身體的反應一樣,有這樣的基礎來學習效率會更好。

最後,我沒有誇張,是真的要花 10 倍以上的時間去練習才能從經驗上學會使用這個工具,

系列文章

  1. 有效率學習一門技術的訣竅 - 閱讀:練習 (1:9)
  2. 有效率學習一門技術的訣竅 - 讓需求引導學習
  3. 有效率學習一門技術的訣竅 - 避開學習障礙
  4. 有效率學習一門技術的訣竅 - 當個容易被幫助的人
  5. 有效率學習一門技術的訣竅 - 突破靠想像力

米邦第二年心得

| Comments

下個月是創業以來的第 24 個月,還記得幾年前我是那種充滿傲氣的程式人,覺得憑著熱情、努力,再加上邏輯分析能力,能夠解決創業大大小小的事情。不過幾個月來的事讓我發現其實根本不是這麼回事。

最近這一年,是我在專業領域成長最多的一年,我有機會可以不受干擾的帶領整個 team 包含那些我不熟悉的技術,我可以嘗試一些敏捷方法、選擇 solution、並學習如何去信任同事、和向下授權。

以前我認為創業最不好玩的那些 ... 人的問題、管理問題,都在學習找方法的過程中解決了,最後情況甚至是那些教敏捷開發書中寫的一樣,以前覺得是神話的事情都慢慢發生,團隊自主、事情變得可估計、可被控制,對我而言這些就像是剛學寫程式那種雀躍的感覺。

但是當軟體技術、人、管理都不是問題的時候,我發現真正的難題還是回歸到商業本身,我們面臨的問題是,當要與其他公司合作做資源交換、整合,這種組織對組織的關係,是無法憑著少少人的承諾或信任就能達成,而通常談得下合作的人,不一定敲的定細節,敲的定細節的人可能談不下合作,所以如何讓這些關係開始、而且順暢,變得很關鍵。

只能說我的經驗太少,所以我有點訝異,創業中不好玩的事情遠遠超過我原本的想像。以前當員工的時候,討厭公司內部的政治問題而離職創業,創業以後才發現公司外有更多政治問題 XD

rails 筆記 - 重構計畫

| Comments

為了要有更好的系統擴充能力,在發展系統功能的時候我們常常要在 refactor 與新功能間奮戰,今天被公司的人問到該怎麼估 refactor 計畫的時程,以下是我自己在開發時的方法。

開發過程中就要 list refactor idea

像是 refactor 這種沒有終點的行為,應該在開發功能時,為了避免完美主義手癢把軟體過度工程化,我通常會先以 user story 上的目標盡快完成,如果了解未來軟體擴充方向,在開發的時候可以把一些 refactor 的 idea 先 list 起來。

如果說 user story 對應到的是一個商業價值的話,refactor idea 對應到的則是一個有利於系統或開發的價值,也許是增加 performance、也許是增加設計彈性、也許是 DRY 未來的程式碼。

不過現在覺得有價值的東西不一定未來真的會需要用到,通常系統怎麼發展還是要看商業上怎麼發展,而且很多時候的需求其實都只是自己的假設而已,所以 list 這些 idea 我會順便紀錄下當時列出的原因跟目的,例如:

  1. 把 xxx 資料物件化 (未來在 yyy 擴充需求時會用到)
  2. 把某些資料計算改成 lazy loading 並 cache 起來 (增加運作時的 performance、避免不必要的計算)
  3. 把某些資料改以 callback 方式來組織增加橫向擴充能力 (未來增加 xxx, yyy, zzz 功能時不需影響主體)

DONT DO

以前做法不順暢的原因通常是把 開發Refactor 拆成兩件事情,導致當非不得已需要 refacotoring 的時候才開始想要怎麼做,然後就會發生:有能力計畫的人時間被塞爆,剩下的人閒置又無法開發新功能。

Planning

只要在開發中有去思考以上這些問題,第二階段要 refactoring 的時候其實就像在做 sprint planning 一樣,依照目前的發展狀況對最有價值或最重要的 ticket 先做就可以了。 (把 idea 拖到要開始 refactor 階段後會發現很多東西其實都不需要實作)

如果在開發中產生的 idea 可能過於抽象或太大,確定要做的 ticket 這時候可以釐清細節並拆成更小的 ticket,這時候的 ticket 就是估得出來的時間了。

捨比取重要

另外我想在面對 技術問題 的時候很多人都會有這樣感想:選擇不做什麼 比起 要做什麼 是還要難決定的事情,因為每樣東西感覺都有其價值,差別是這個價值反映在商業上是 現在未來 亦或是 另一個平行宇宙,所以最好的狀況還是 RD 貢獻的產出能隨著公司發展而發展。

實踐心得

其實這些想法是基於上過 xdite 的敏捷開發課程,對我而言課程中最重要的是 planning 部分,而我認為好的 plan 最重要的是 效率和諧的流程,就是那種大家把自己的 ticket 做完就莫名其妙沒事了那種感覺。

我在嘗試實踐以上想法的過程中,第一個改掉的習慣是 停止想像力的發揮 把想出來的東西轉化成明確的 issue,雖然說這過程不容易、也一點都不好玩,不過這樣做以後確實讓我的 team work 的很順利。

Rails Sharing #2 - 當 controller create 來自不同頁面時的處理

| Comments

現在要討論的問題是,如果我想在一個 resource 裡面的 #index 有一個簡化的新增表單、#new 裡面是一個完整的新增表單。

在 #index 裡面的做法其實跟 #new 是一樣的,真正的問題是,當送過去 #create 時,如果 validate 不過的處理。

app/controllers/posts_controller.rb
# POST /posts

def create
  @post = Post.new(post_params)
  if @post.save
    redirect_to some_where_path, notice: 'successful'
  else
    render :new # 重點是這個地方

  end
end

在 #7 行render :new 的地方,如果你的流程設計是從 #index post 過來 validate 不過時,就重新 render :new 的話,以上程式碼完全沒問題 (某些網站是這樣做的,比如說:首頁有登入畫面,如果登入失敗則離開首頁,顯示完整登入的 view)。

但如果你希望設計的流程是,在 #index 填寫送出新增 validate 不過維持在 #index,#new 填寫送出新增不過則 re-render :new 的話,那就必須在 #create 裡面做特別處理了 (至少要讓 create 知道該怎麼 re-render view)

不過這又會有另一個問題,render 只是幫你去 render 某個 view 並不會幫你跑屬於那個 view 的 action,所以假設我有以下程式碼,就會有更多邏輯要處理。

app/controllers/account/posts_controller.rb
# GET /account/posts

def index
  @post = current_user.posts.newest.page(params[:page])
end

# GET /account/posts/new

def new
  # .. (略)

end
app/controllers/account/posts_controller.rb
# GET /account/posts

def create
  @post = Post.new(post_params)
  if @post.save
    redirect_to some_where_path, notice: 'successful'
  elsif params[:from_action] == 'index'
    render :index
  elsif params[:from_action] == 'new'
    render :new
  end
end

然後再 #index 的 form 裡面這樣實作:

app/views/account/posts/index.html.erb
<%= form_for([:account, @post]) |f| %>
  <input type="hidden" name="form_action" value="index">
<% end %>

我們先不討論 from_action 的部分,上面的程式碼當你 validate 不過時判斷從哪裡 post 過來的問題是:重新 render 了 :index view 但這時並沒有跑 #index 的 action,等於你跑了那個 view 卻沒有 @posts,當然你可以加上。

def create
  # ... (略)

  @posts = current_user.posts.newest.page(params[:page])
  render :index
  # ... (略)

end

這種情況假設你的 PostController#index 要做的事情越多,你的程式碼越髒,另一種會讓你更難維護的狀況是,假設你的 form 擺在各種不同的地方,假設:首頁放一個、posts#index 放一個、聯絡我們也放一個。以上這種方法就會非常不好維護。

所以如果是這種狀況我的做法會以 "有幾種 form 來切割" 去取代以上的 "以幾種來源來切割",假設整個網站的 posts#create 的 form 總共有兩種,一種精簡的、一種完整的,再搭配 SRJ (Server side response javascript) 的做法,利用 AJAX 只更新正在操作的那張 form html 就可以了,我的 form 會有以下兩張。

# app/views/account/posts/_lean_form.html.erb (精簡的 form)
<%= form_for([:account, @post], remote: true, format: :js, html: { id: 'post_lean_form' }) do |f| %>
  <input type="hidden" name="form_type" value="lean_form">
  <!-- 略 --!>
<% end %>

# app/views/account/posts/_form.html.erb (完整的 form)
<%= form_for([:account, @post], remote: true, format: :js, html: { id: 'post_form' }) do |f| %>
  <!-- 略 --!>
<% end %>

有需要精簡表單的,我可以在任意地方 render 'account/posts/lean_form'

# app/views/account/posts/new.html.erb
<h1>Hello New page</h1>
<%= render 'new' %>

# app/views/home/index.html.erb
<h1>Home Page</h1>
<%= render 'account/posts/lean_form' %>

接下來才是重頭戲,我要如何使用 SRJ 去 handle post request 而不去刷新頁面。

app/controllers/account/posts_controller.rb
# POST /account/posts.js

def create
  respond_to do |format|
    # 僅允許 .js 的 request

    format.js {
      @post = current_user.posts.new(post_params)
      @status = @post.save
    }
  end
end

當你限定 #create 僅允許 js 時,其他 format 的 request 都會被以 UnknowFormat 的 exception 擋下來,跟之前做法的差異是,我們不在controller 內處理 redirect / re-render 邏輯,而是把 valid 的結果存到 @status,留到 javascript 去控制瀏覽器,接下來我們需要實作 create.js.erb

app/account/posts/create.js.erb
// SRJ 這裏習慣用 function 包起來在執行避免污染 javascript 全域變數
(function(){
  <% if @status %>
    window.location.href = '<%= post_path(@post) %>';
  <% else %>
    <% if params[:form_type] == 'lean_form' %>
      $form = $('<%=j render 'lean_form' %>');
      $('#post_lean_form').html($form.html());
    <% else %>
      $form = $('<%=j render 'form' %>');
      $('#lean_form').html($form.html());
    <% end %>
  <% end %>
});

以上的 #create 如果 save 成功,則瀏覽器會執行導頁到 posts/:id 的頁面,而 valid 不過的狀況,會依照是哪個類型的 form 去選擇 render 在 replace 掉畫面上的 form,這樣一來就完全不必管,是從哪裡來,還要特別處理那些撈資料的問題了。

完成 :)

rails 筆記 - 物件設計心得

| Comments

一般的情況在 MVC 下寫程式,能夠設計的物件頂多是 model 拆拆 concerns、寫寫抽象層、或把邏輯封裝到 form/service object 裡,而這些 class 最終都是用在 controller, views,不太有機會去實作一個需要考量 如何與其他物件互動 的物件。

最近這幾個月寫遊戲後端程式讓我有機會好好思考這些問題:

我該如何設計、組織這些複雜的邏輯並且容易維護。

一開始我很直覺的把遊戲共同邏輯拆出 abstraction layer 希望能夠做到像 ActiveRecord::Base 一樣,我可以開一個 class 繼承抽象類別去客製化遊戲事件跟設定值,例如:Racing::Dog < Racing::Base

開發 Racing::Base 過程中最有感想的是實作遊戲設定值的部分,設計物件的目的,是避免一直使用基礎物件 (Hash, Array, String ... etc) 加上 functional programming 去撰寫程式,假設:我們要尋找遊戲賠率最高的狗,可能會有某個 method 去 array 裡面找資料:

def find_the_best_price_dog(game_model)
  Racing::Dog.find { ... }
end

只使用 基礎型別 加上不斷的靠 method 整理資料最大的問題是,如果某天哪個節點的資料結構改變了,程式就炸掉了。而且會變得非常難 debug,因為你很難一眼看出複雜的 Array, Hash 到底是哪裡出問題。

但如果用物件的設計方式,也許我們會有一個 class 叫做 Racing::PlayerCollection 來儲存每個 player,而 collection 在意義上就像一個大盒子,裝著多個 player 的資料,這個大盒子內建就有一個功能是 best_price_player 幫你找到這個盒子內地最佳賠率 player:

game_model.players # return instance of Racing::PlayerCollection

game_model.players.best_price_player # return { name: 'Adz', type: 'dog', rank: 1, price: 0.95 }

當我們能夠找到賠率最佳的狗時,也許某個地方我們會有這樣的程式碼

if Racing::Dog.players.best_price_player[:type] == 'dog'
  # do something

end

這裏我們又操作到基本型別了,一樣的問題,如果未來設定值的 Hash 結構改變,這種程式碼散布世界各地,就會變得非常難改,所以我們也需要把 player 的資料物件化成 Racing::Player 再把程式改寫成:

player = Racing::Dog.players.best_price_player
player.dogs? # true || false

player.price # 0.95

除了以上提到的問題外,複雜的 array, hash 搭配 functional programming 因為沒有明確的 class name,是非常難以測試的,即使有 test,反而會更自找麻煩,日後結構改變,不只程式要改,連 test 也會改很大。