在 Ruby 2.5 為物件引進了 yield_self 方法, 但是方法名字很長, 因此在 Ruby 2.6 時為他新增了一個別名: then. 這個方法與 tap 類似, 不同的點在於 tap 回傳自己, 但 then 回傳的是執行結果.

善用這兩個方法可以寫出更清楚易懂的程式碼, 並且減少指派 (減少你需要去煩惱 “這個變數該叫什麼名字” 的困擾).

直接來看看一個來自真實世界的範例. 需求是有一系列的圖片, 希望能模糊化且半透明化, 我們使用 image_processing, 而我在閱讀文件後得知. 我大概需要下 -gaussian-blur 0x16 -alpha set -channel A +level 0,20% 這樣的指令去完成我想做的事情. 注意 level 參數是加號開頭的, 在 image_processing 時需要在該參數後面添加加號, 因此我應該要寫成:

# WRONG: NoMethodError (undefined method `call' for "0,20%":String)
image = ImageProcessing::MiniMagick.source(original)
image.convert('png')
     .gaussian_blur('0x16')
     .alpha('set')
     .channel('A')
     .level+('0,20%')
     .call

因為 + 的優先權比較低, 因此 ('0,20%').call 被先行計算了, 我沒辦法舒服的 chain 下去, 難道我只能寫成這樣嗎?

# BAD
image = ImageProcessing::MiniMagick.source(original)
(image.convert('png')
     .gaussian_blur('0x16')
     .alpha('set')
     .channel('A')
     .level+('0,20%'))
     .call

# BAD
image_before_call = image.convert('png')
                         .gaussian_blur('0x16')
                         .alpha('set')
                         .channel('A')
                         .level+('0,20%')
image_before_call.call

想像一下, 日後如果你需要 “加” 更多東西的時候該怎麼辦呢? 優美的 ruby 是不容許這麼難看的東西出現的! 幸好我們有 then 方法, 可以幫我們把那個 .level+ 包起來, 去避免使用大範圍的括號調整優先權, 或者使用一個不知道該怎麼取名的臨時變數.

最後的結果如下:

# GOOD
image = ImageProcessing::MiniMagick.source(original)
image.convert('png')
     .gaussian_blur('0x16')
     .alpha('set')
     .channel('A')
     .then { _1.level+('0,20%') }
     .call

使用 tapthen 你幾乎可以 chain 任何你想 chain 的東西, 請善用它們, 讓程式碼看起來更舒服吧.