如何使用 pry 作為 Ruby debugger 進行除錯

很奇怪,ruby-debug 或是 ruby-debug19 常常有版本問題,甚至還有 fork 版本 debugger。都不知道哪個沒問題。

最近發現其實 pry gem 就可以作為 debugger 之用,本來還以為只是漂亮的 irb 取代品,沒想到功能這麼強大。作為 debugger 使用的方法很簡單,只要在程式裡加上:

binding.pry

這樣就會設下中斷點。例如在 rails console 或 rails server 中,就會停下來讓你可以進行檢查和除錯。

可是如果你有用 Rails 伺服器,例如 pow 或 passenger 的話,因為不在同一個 process,所以必須加裝 pry-remote gem。它利用 DRb 技巧讓另一個 process 可以亂入進行除錯,用法改成:

binging.remote_pry

接著在 command line 輸入 pry-remote 就可以進行除錯了。非常簡單好用。

其他 pry plugins 也可以裝一下,包括:1. pry-stack_explorer 輸入 show-stack 的話可以看到 call stack 2. pry-debugger 可以加上 step, next, finish 和 continue 的控制

A brief introduction to Vagrant – 原來 VirtualBox 可以這樣玩 投影片

這是今年在 OSDC.TW 演講的投影片。這次的內容相對簡單,就分享一個好用的工具給大家。會挑這個題目並不是因為自己對 Vagrant/Chef 非常熟很有研究(我們公司的 DevOps 當然比我厲害)。而是從一個 Rubyist 的角度想說 Ruby 社群裡有什麼好東西可以介紹給大家。畢竟 OSDC.TW 並不是以特定技術為主的研討會,來聽的人四面八方,所以從去年開始我就盡量考慮聽眾適合聽的程度,以後大概也會抱持這樣的想法吧。

Sublime Text 資源整理

自從 Textmate 2 官方開發陣亡之後,就改用 Sublime Text 2 來寫 code 了。整理一些找到的好資料跟 Plugins:

Plugins

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 來改,步驟如下:

  1. 下載 Amazon RDS Command Line Toolkit

  2. 參考 credential-file-path.template,編輯 credentials.txt 把 AWSAccessKeyId 和 AWSSecretKey 填上去

  3. 設定環境變數,例如:
    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

  4. chmod +x ${AWS_RDS_HOME}/bin/*

  5. rds-create-db-parameter-group <自定的parameters群組名稱> -d "群組描述" -f MySQL5.5 新增一個群組來放參數

  6. rds-modify-db-parameter-group <自定的parameters的群組名稱> -p "name=max_allowed_packet,value=2097152,method=immediate" 把參數放進你自定的群組裡 (這裡單位只能用bytes)

  7. rds-modify-db-instance <DB Instance Name> -g <自定的parameters群組名稱> 把群組綁到指定的 DB 上

  8. 這個設定似乎不需要 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特性來做),不過用的人就不多了。

結論

  1. 在系統邊界內,盡量相信輸入,不做檢查
  2. 如果需要檢查,採用有錯誤就中斷掉的方式(Fail-fast),避免 defensive programming 把問題藏起來

後話

另一個會讓整個系統充滿防禦性風格的情況,可能發生在團隊感情不好的環境。為什麼呢? 因為溝通不良,加上沒有適當的規範,所以寫code的時候只好處處防著別人,為了怕 crash 要麻煩別人(caller),只好額外費工加一大堆多餘的條件檢查和處理,把問題都埋起來好讓方法不會crash…

參考資料

註: 有些人認為 “fail fast” 也算是 defensive programming 的一招。我這裡採用對比的 offensive programming 說法。