這是今年在 OSDC.TW 演講的投影片。這次的內容相對簡單,就分享一個好用的工具給大家。會挑這個題目並不是因為自己對 Vagrant/Chef 非常熟很有研究(我們公司的 DevOps 當然比我厲害)。而是從一個 Rubyist 的角度想說 Ruby 社群裡有什麼好東西可以介紹給大家。畢竟 OSDC.TW 並不是以特定技術為主的研討會,來聽的人四面八方,所以從去年開始我就盡量考慮聽眾適合聽的程度,以後大概也會抱持這樣的想法吧。
Sublime Text 資源整理
自從 Textmate 2 官方開發陣亡之後,就改用 Sublime Text 2 來寫 code 了。整理一些找到的好資料跟 Plugins:
- Sublime Text 2 for Ruby
- 轉換心情, Sublime Text 2
- MY TOP 10 SUBLIME TEXT PLUGINS
- Setting up Sublime Text 2
- Sublime Text 2 – Useful Shortcuts
- Sublime Text Cheatsheets and Snippets
- Sublime Text 台灣 Google+
- Sublime Text 台灣 Twitter
- Perfect Workflow in Sublime Text 2
Plugins
- Package Control 可以方便管理 packages
- SideBarEnhancements 增強側欄功能
- gitgutter 顯示 ++ — 的 git diff
- EasyMotion 很有趣的快速純鍵盤游標移動
- Git Plugin
- All Autocomplete Sublime Text 增強 autocomplete
Syntax 相關
Settings
編輯 Preferences > Settings > User 加上:
{
# 把 tab 換兩個空白字元
"translate_tabs_to_spaces": true,
"tab_size": 2,
# 高亮所在行
"highlight_line": true,
# 存檔時移除多餘的空白
"trim_trailing_white_space_on_save": true
}
還有推薦什麼必裝的 Plugins 嗎?
修改 AWS RDS 資料庫設定,以 max_allowed_packet 為例
記錄一下,以後肯定還會再用到。
因為 MySQL 預設的 max_allowed_packet 設定是 1M,對有些應用來說可能太小了。如果是自己架 MySQL 改 /etc/mysql/my.cnf 就是了,不過租用 Amazon RDS 就沒有這種事了。又它的 web console 也沒有提供介面可以改設定(parameters)除了可以用 AWS 的 web console 介面來改,也可以透過 Amazon RDS Command Line Toolkit 呼叫 API 來改,步驟如下:
-
參考 credential-file-path.template,編輯 credentials.txt 把 AWSAccessKeyId 和 AWSSecretKey 填上去
-
設定環境變數,例如:
export AWS_RDS_HOME=/Users/ihower/RDSCli-1.12.001
export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Home
export PATH=/Users/ihower/RDSCli-1.12.001/bin:$PATH
export AWS_CREDENTIAL_FILE=$AWS_RDS_HOME/credentials.txt
export EC2_REGION=ap-northeast-1
-
chmod +x ${AWS_RDS_HOME}/bin/*
-
rds-create-db-parameter-group <自定的parameters群組名稱> -d "群組描述" -f MySQL5.5
新增一個群組來放參數 -
rds-modify-db-parameter-group <自定的parameters的群組名稱> -p "name=max_allowed_packet,value=2097152,method=immediate"
把參數放進你自定的群組裡 (這裡單位只能用bytes) -
rds-modify-db-instance <DB Instance Name> -g <自定的parameters群組名稱>
把群組綁到指定的 DB 上 -
這個設定似乎不需要 reboot 就生效了。要重開的話,在 web console 也可以 reboot。
完整的文件在 AWS Command Line Reference,例如
rds-describe-db-instances <DB Instance Name> 看這個 DB 套用了什麼 PARAMGRP 群組
rds-describe-db-parameters <自定的parameters的群組名稱> 看這個群組的 parameters
A brief introduction to SPDY – 邁向 HTTP/2.0 – 投影片
這是這次在 WebConf Taiwan 演講的投影片。從接到演講邀請就想說講什麼題目最切題,雖然自己比較熟的是 Rails,但是好像從前年開始就沒有在公開的場合講 Rails 了(咦?) anyway,這次一開始投的是「如何善用 REST 和 Hypermedia 設計 Web APIs」,後來看 SPDY 覺得這更實用且影響更大,更值得介紹給大家。加上我自己之前看 HTTP 連線管理 就覺得這議題蠻有趣的。REST 只是一種設計風格,而 HTTP 可是大家賴以為生的通訊協定啊,所以就毅然決然決定來改題目…
會後似乎獲得了不錯的回響,看來很多人都是第一次認識 SPDY 啊,題目真是挑對了 XD
Defensive Programming 防禦性程式設計
TL;DR 避免 Defensive Programming,愛用 Fail Fast 策略。
什麼是防禦性程式設計,在 Erlang’s Programming Rules and Conventions: Do not program “defensively” 裡這樣說明:
防禦性程式發生在程式設計師不相信系統的輸入(input)資料時。一般來說我們不需要測試輸入的資料來確保功能正常。系統中大部分程式碼應該可以假設輸入的資料是正確的,只有一小部分的程式需要真的檢查資料,這通常發生在資料第一次輸入的時候。只要資料在輸入進系統時被檢查過,那麼就應該可以假設它是正確的。
也就是說,當你不相信輸入時,你就得去檢查它。有經驗的程式設計師應該都非常熟悉這種作法,他的缺點在於這常是多餘的資源浪費,不但浪費效能,也增加程式碼維護的成本。好比說一個方法已經檢查過參數了,該方法又繼續把參數傳遞給別的方法時,可能又要再檢查一次。如果所有方法都要針對參數重複做嚴格的檢查,豈不是非常囉嗦。所以 Erlang’s Programming Rules 建議你在系統裡不要檢查,crash 與否的責任在於呼叫方,在於資料一開始輸入進系統的時候,而不是這個方法。
但是,總是有需要檢查的時候,特別是資料第一次輸入進系統的邊界情況,以應用程式來說,就是指使用者的輸入; 以 Library 來說,就是指 Public API。這時候有兩種策略來對付不乖的參數: 1. 修正或略過資料的錯誤(defensive, compensate) 2.丟出錯誤(offensive, fail fast)
讓我們來看看例子吧。以 Check Null 型的問題來說,有三種寫法:
# defensive programming
# 如果參數不符合條件,就略過或修正它
def resize_image(image)
if image && image.is_a?(File) # 檢查 image 不是 nil,而且是 File
# execute code
else
# ingore, nothing happened.
end
end
# non-defensive programming
# 不檢查,錯了就讓他 crash 吧
def resize_image(image)
# execute code
end
# offensive programming (fail fast)
# 檢查,有錯就馬上失敗讓你知道
def resize_image(image)
raise "The image is invalid" unless ( image && image.is_a?(File) )
# execute code
end
Daniel Roop 在 Why Defensive Programming is Rubbish 認為 defensive 策略是一種 Hide the Problem Programming,我覺得講的很好。為什麼呼叫方會傳不對的值進來呢? 這是應該要去 trace 的問題,而不是把問題隱藏起來。長久以往下來,整個系統的不確定性就會越來越多,也就越來越難維護。
比較建議的作法是,如果是系統內可以相信呼叫方,那就不要檢查了。如果是邊界情況需要檢查,就做 offensive programming,也就是 “Fail Fast”,把條件限制當做一種契約,如果呼叫方(caller)不照規矩來,整個方法就會 fail 掉,這樣的好處是可以提早發現問題,並且在問題發生的第一時間就可以修好,讓整個系統朝向可以互相信任的方向成長。
再舉一個更惡名昭彰的 defensive programming 的例子:
# defensive programming
def run
# execute code
rescue # 救回所有例外
end
此例中,無論發生什麼錯誤,這個方法都會回傳 nil,非常安全不會 crash。但是即使 execute code 的地方打錯字也不會怎樣,徹底把問題隱藏起來了。
可是….
可是我們設計API的時候,特別是像 Ruby 這種動態語言,常常允許參數很有彈性耶? 例如
# 參數 i 除了可以是數字之外,也可以是字串,例如 "123"
def process_integer(i)
var = i.to_i # 轉數字
# process var
end
# 參數 s 除了可以是字串之外,也可以是 Symbol 或數字等
def process_string(s)
var = s.to_s # 轉字串
# process var
end
# 參數 a 除了可以是陣列之外,也可以只傳一個元素或nil
def process_array(a)
arr = Array(a) # 讓 arr 一定是陣列
arr.each do |x|
# process x
end
end
好吧,這種算是”有意 (intentional)”的 API 設計,我們擴大了參數的彈性,而且呼叫方(caller)明確知道可以這樣呼叫(最好搭配API文件)。在 Confident Ruby 這本書裡,提到了很多 Ruby 適用的技巧。
Contract by Design
另一種解決檢查參數前置性條件的根本方法,叫做”契約式設計 Contract by Design”,也就是在語言層面去宣告前置條件。不過,大部分的程式語言都沒有內建,Ruby 是有一些第三方套件(利用Ruby的meta-programming特性來做),不過用的人就不多了。
結論
- 在系統邊界內,盡量相信輸入,不做檢查
- 如果需要檢查,採用有錯誤就中斷掉的方式(Fail-fast),避免 defensive programming 把問題藏起來
後話
另一個會讓整個系統充滿防禦性風格的情況,可能發生在團隊感情不好的環境。為什麼呢? 因為溝通不良,加上沒有適當的規範,所以寫code的時候只好處處防著別人,為了怕 crash 要麻煩別人(caller),只好額外費工加一大堆多餘的條件檢查和處理,把問題都埋起來好讓方法不會crash…
參考資料
- Why Defensive Programming is Rubbish
- Defensive programming (wikipedia)
- Avoid Defensive Programming, Fail Fast Instead
- Offensive Programming
- Failfast programming for the lazy programmer
- Confident Code
- Be Assertive with Sane Exception Handling
- 程式設計範式與OOP的思考術 p.186
- Complete Complete 2nd. 第八章
- The Pragmatic Programmer 第四章
註: 有些人認為 “fail fast” 也算是 defensive programming 的一招。我這裡採用對比的 offensive programming 說法。
Kent Beck 的四個簡單程式設計原則
什麼是好設計,打從開始 coding 以來就是一個不斷追求的大哉問,從
SOLID 看到 Design Patterns,每一個原則看起來好有道理,每一個設計模式都在等著你的套用。
但是最近幾年的工作讓我有不一樣的心得,而不再費心追求「完美」的設計。同一個問題,每個人都會想出不一樣的解法,都說自己的解比較好。於是這些設計原則,常就淪為爭論的工具,當我覺得你的解法做太多事情時,搬出YAGNI; 當我覺得你的解法太複雜時,搬出KISS; 當覺得一個類別太肥大的時候,就搬出 SRP,反正都可以找到支持的論點就對了。很多時候其實不管哪一種方案,都是可以接受的可行方案,於是後來我就不想去爭個你死我活、斤斤計較當下的細節設計了。好設計應該是演進(grow)出來的,我首先在乎的反而是有沒有留下可用的測試和清楚好讀的程式,這樣才可以留下(後人)改進的機會。
回到正題,Kent Beck 在 eXtreme Programming 中,對於什麼是 Simple Design,留下了四個 Xp Simplicity Rules,我個人蠻喜歡的,因為很簡單又基本:
-
Pass All Tests 通過全部測試:有測試才能夠讓軟體不斷的演進(grow)下去,不然你不是被 regression bugs 淹死,就是只能不計成本把code砍掉重練。如何寫好測試也是個大哉問,xUnit Test Patterns: Refactoring Test Code一書是其中的bible,嫌厚的話,可以先看看Refactoring Test Code這一篇paper,都是在講重構你的測試程式。當然,事情總是過猶不及,老闆付錢給你是為了得到 code,不是 tests。測試只是幫助我們寫好code的工具,追求100%完整的好測試並不是目的。
-
Reveals Intent (Self-Documenting Code) 程式能夠表達出意圖:其中最重要而且基本的,就是要有好的程式命名,無論是類別、方法、變數等等。專講 Readable 的書有一本可以推薦 The Art of Readable Code。我這裡還推薦一個我最喜歡的技巧 Composed Method (搞笑談軟工也有一篇介紹),這招也是 Kent Beck 大師提出的,將細節的實作抽取成一個小方法,讓整個方法裡的操作都是類似的粒度,小小一招,大有作用。
-
No Duplication (DRY) 不重複:”Every piece of knowledge must have a single, unambiguous, authoritative representation within a system” 完整定義請複誦三次。有時候重複並不是這麼明顯,例如作用重複的程式,但命名不同,或是命名重複,但程式作用不一樣。這裡推薦 David Chelimsky 的影片 Maintaining Balance While Reducing Duplication (slides)和 Maintaining Balance while Reducing Duplication: Part II,可以增進對 DRY 的理解,DRY 不只是在講不要重複程式碼而已。
-
Has no superfluous parts (Minimizes the number of classes and methods) 不多餘:這一條也就是 YAGNI,不要實作多餘的功能。我個人不怎麼喜愛這個原則。不是不贊同,不需要的功能花時間去寫當然是浪費大家時間,而是這個原則太容易例外了。有時候會寫下多餘的程式來增加功能或擴充性,那是因為你擁有的經驗、直覺跟當時的情境告訴你應該這麼做,這可以在不久的將來就派上用場,過於強調 YAGNI 反而一板一眼教條主義了。光單看”減少類別跟方法的數量”跟 SRP 和 Composed Method 就有所衝突了,後兩者都會增加類別和方法的數量。
重要性 1 > 2 > 3 > 4,也有版本講 1 > 3 > 2 > 4。