Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Refactoring (Ruby edition)

Refactoring (Ruby edition)

高見龍

May 31, 2018
Tweet

More Decks by 高見龍

Other Decks in Programming

Transcript

  1. 從好到更好
    高見龍 @ 五倍紅寶石
    程式碼重構

    View full-size slide

  2. a.k.a Eddie
    愛現! 喜歡冷門的玩具
    Ruby/Rails/iOS app 開發者、講師
    Ruby 技術推廣、教育、諮詢
    台灣、日本等國內外 Ruby 技術研討會講者
    目前於五倍紅寶石擔任紅寶石鑑定商職務
    部落格:https://kaochenlong.com
    高見龍
    photo by Eddie
    @eddiekao

    View full-size slide

  3. 發售中!
    https://railsbook.tw/

    View full-size slide

  4. 發售中!
    https://gitbook.tw/

    View full-size slide

  5. • 什麼是重構?
    • 動手作! (50 mins)
    • 何時該進行重構?
    • 何時不該進行重構?
    • 程式碼的壞味道
    • 常見重構手法介紹
    • Ruby 風味之程式整理手法

    View full-size slide

  6. 參考資料

    View full-size slide

  7. 什麼是重構?

    View full-size slide

  8. 程式碼會慢慢腐爛...

    View full-size slide

  9. 不是每個專案都能砍掉重練!

    View full-size slide

  10. 重構:
    「在不改變程式碼外在行為的
    前提下,對程式碼做出修改,
    以改進程式的內部結構」

    View full-size slide

  11. 重構不是藝術,重構是工程

    View full-size slide

  12. 重構有風險

    View full-size slide

  13. 編譯器不會在乎你的程式碼好不好看

    View full-size slide

  14. 如果沒壞,就別動它?
    它是沒壞,但會讓你的生活變得比較難過...

    View full-size slide

  15. 先決條件:測試
    只要是人,都會犯錯

    View full-size slide

  16. Let’s Coding
    https://github.com/kaochenlong/code_refactoring_demo

    View full-size slide

  17. 重構的節奏
    小修改→測試→小修改→測試→小修改→測試...

    View full-size slide

  18. 重構就只是整理程式碼?

    View full-size slide

  19. 重構的目的是使軟體更容易被理解
    及修改。

    View full-size slide

  20. 重構不會改變軟體

    可受觀察之行為

    View full-size slide

  21. 「我不是個偉大的程式員,我只是個
    有著一些優秀習慣的好程式員而已」
    - Kent Beck

    View full-size slide

  22. 下手的好對象:
    • 區域變數
    • 迴圈
    • 條件式
    • 註解

    View full-size slide

  23. 何時該進行重構?

    View full-size slide

  24. 重構不是一件需要特別撥
    出時間來做的事情

    View full-size slide

  25. 不要為重構而重構

    View full-size slide

  26. 加新功能、修正錯誤、Code
    review 時都可進行重構。

    View full-size slide

  27. 重構有助於了解別人(或不
    久前自己)的程式碼。

    View full-size slide

  28. 事不過三原則

    View full-size slide

  29. 要怎麼說服你的長官
    進行重構?

    View full-size slide

  30. 如果講不聽,你就自己做吧
    畢竟最後還是你要來維護這個專案

    View full-size slide

  31. 程式碼被閱讀及修改的次數,
    遠多於它被編寫的次數。

    View full-size slide

  32. 何時不該進行重構?

    View full-size slide

  33. 需要砍掉重練的時候!

    View full-size slide

  34. 時間剩下不多的時候!

    View full-size slide

  35. 程式碼的壞味道

    View full-size slide

  36. 知道 How,也要知道 When

    View full-size slide

  37. 壞味道:
    重複的程式碼
    duplicated code
    可用手法:
    • Extract Method
    • Pull Up Method

    View full-size slide

  38. 壞味道:
    過長的方法
    long method
    可用手法:
    • Extract Method
    • Replace Temp with Query
    • Replace Method with Method Object

    View full-size slide

  39. 壞味道:
    過大的類別
    large class
    可用手法:
    • Extract Class

    View full-size slide

  40. 壞味道:
    一個方法有過多的參數
    long parameter list
    可用手法:
    • Replace Parameter with Method

    View full-size slide

  41. 壞味道:
    Switch 現身
    switch statements
    可用手法:
    • Replace Type Code with Polymorphism

    View full-size slide

  42. 壞味道:
    暫時欄位
    temporary field
    可用手法:
    • Extract Class
    • Replace Method with Method Object

    View full-size slide

  43. 壞味道:
    過多的註解
    comments
    可用手法:
    • Extract Method
    • Rename Method

    View full-size slide

  44. 常見重構手法介紹

    View full-size slide

  45. 提煉方法
    Extract Method
    • 一個方法做太多事
    • 方法的長度不是問題,問題在於方法本身是
    不是能明確表達它要做的事
    • 用「做什麼」,不要用「怎麼做」來命名
    • 區域變數有時也要一併傳進新的方法

    View full-size slide

  46. def print_owing(amount)
    print_banner
    puts "name: #{@name}"
    puts "amount: #{amount}"
    end
    使⽤用前

    View full-size slide

  47. def print_owing(amount)
    print_banner
    print_details amount
    end
    def print_details(amount)
    puts "name: #{@name}"
    puts "amount: #{amount}"
    end
    使⽤用後

    View full-size slide

  48. 方法內聯化
    Inline Method
    • 一行程式就能做完的,不一定要另外給它一
    個方法
    • 如果方法本身就能明確表達它的意思,不用
    特別拆成兩個。

    View full-size slide

  49. def get_rating
    more_than_five_late_deliveries ? 2 : 1
    end
    def more_than_five_late_deliveries
    @number_of_late_deliveries > 5
    end
    使⽤用前

    View full-size slide

  50. def get_rating
    @number_of_late_deliveries > 5 ? 2 : 1
    end
    使⽤用後

    View full-size slide

  51. 以查詢取代暫時變數
    Replace Temp with Query
    • 暫時變數就只是暫時的...
    • 如果暫時變數只被賦值一次
    • 效能問題?

    View full-size slide

  52. base_price = @quantity * @item_price
    if base_price > 1000
    base_price * 0.95
    else
    base_price * 0.98
    end
    使⽤用前

    View full-size slide

  53. if base_price > 1000
    base_price * 0.95
    else
    base_price * 0.98
    end
    def base_price
    @quantity * @item_price
    end
    使⽤用後

    View full-size slide

  54. 以方法鍊結取代暫時變數
    Replace Temp with Chain
    • 方法執行後回傳物件
    • 在 Ruby / Rails 常見,可減少程式碼行數。

    View full-size slide

  55. mock = Mock.new
    expectation = mock.expects(:a_method_name)
    expectation.with("arguments")
    expectation.returns([1, :array])
    使⽤用前

    View full-size slide

  56. mock = Mock.new
    mock.expects(:a_method_name).with("arguments"
    ).returns([1, :array])
    使⽤用後

    View full-size slide

  57. 暫時變數內聯化
    Inline Temp
    • 通常是 Replace Temp with Query 手法的一部
    份。

    View full-size slide

  58. base_price = an_order.base_price
    return base_price > 1000
    使⽤用前

    View full-size slide

  59. return an_order.base_price > 1000
    使⽤用後

    View full-size slide

  60. 引入解釋性變數
    Introduce Explaining Variable
    • 把複雜的運算放進暫時變數,用變數名稱來
    解釋其用途
    • 也可使用 Extract Method 來取代之

    View full-size slide

  61. if (platform.upcase.index("MAC") &&
    browser.upcase.index("IE") &&
    initialized? &&
    resize > 0
    )
    # do something
    end
    使⽤用前

    View full-size slide

  62. is_mac_os = platform.upcase.index("MAC")
    is_ie_browser = browser.upcase.index("IE")
    was_resized = resize > 0
    if (is_mac_os && is_ie_browser &&
    initialized? && was_resized)
    # do something
    end
    使⽤用後

    View full-size slide

  63. 剖解暫時變數
    Split Temporary Variable
    • 不要讓同一個暫存變數做不同的用途

    View full-size slide

  64. temp = 2 * (@height + @width)
    puts temp
    temp = @height * @width
    puts temp
    使⽤用前

    View full-size slide

  65. perimeter = 2 * (@height + @width)
    puts perimeter
    area = @height * @width
    puts area
    使⽤用後

    View full-size slide

  66. 以方法物件取代方法
    Replace Method with Method Object
    • 有時候方法太複雜,無法用 Extract Method
    或 Replace Temp with Query 手法處理…
    • 新增一個類別,把方法包進去

    View full-size slide

  67. class Account
    def gamma(input_val, quantity, year_to_date)
    important_value1 = (input_val * quantity) + delta
    important_value2 = (input_val * year_to_date) + 100
    if (year_to_date - important_value1) > 100
    important_value2 -= 20
    end
    important_value3 = important_value2 * 7
    # and so on.
    important_value3 - 2 * important_value1
    end
    end
    使⽤用前

    View full-size slide

  68. class Account
    def gamma(input_val, quantity, year_to_date)
    Gamma.new(self, input_val, quantity,
    year_to_date).compute
    end
    end
    使⽤用後

    View full-size slide

  69. 以集合閉包方法取代迴圈
    Replace Loop with Collection Closure Method
    • 可縮短程式碼行數,但不見得保證能增加程
    式碼可讀性
    • 不是每種程式語言都有支援

    View full-size slide

  70. managers = []
    employees.each do |e|
    managers << e if e.manager?
    end
    使⽤用前

    View full-size slide

  71. managers = employees.select {|e| e.manager?}
    使⽤用後

    View full-size slide

  72. 方法上移
    • DRY = Don’t Repeat Yourself
    • 把子類別重複的方法提到上層父類別
    Pull Up Method

    View full-size slide

  73. class Person
    attr_reader :first_name, :last_name
    def initialize first_name, last_name
    @first_name = first_name
    @last_name = last_name
    end
    end
    class Male < Person
    def full_name
    first_name + " " + last_name
    end
    def gender
    "M"
    end
    end
    使⽤用前
    class Female < Person
    def full_name
    first_name + " " + last_name
    end
    def gender
    "F"
    end
    end

    View full-size slide

  74. class Person
    attr_reader :first_name, :last_name
    def initialize first_name, last_name
    @first_name = first_name
    @last_name = last_name
    end
    def full_name
    first_name + " " + last_name
    end
    end
    class MalePerson < Person
    def gender
    "M"
    end
    end
    使⽤用後
    class FemalePerson < Person
    def gender
    "F"
    end
    end

    View full-size slide

  75. 重新命名方法
    Rename Method
    • 命名需要經驗,還有英文字要認識的夠多
    • 通常無法第一次就給方法取個好名字
    • 在舊方法呼叫新方法
    • 舊的方法不要砍,只要先標記 deprecated

    View full-size slide

  76. 搬移方法
    Move Method
    • 如果一個類別有太多方法,或是與另一個類
    別黏太緊...
    • 有時不容易下決定,需要經驗值!
    • 適當使用委託方法(delegation method)

    View full-size slide

  77. 搬移欄位
    Move Field
    • 一開始的設計不良...
    • 如果是 public field,可先寫個 public getter/
    setter 來包裝它

    View full-size slide

  78. 提煉類別
    Extract Class
    • 每個類別應該都有它明確的責任
    • 隨著時間,責任越來越重,這樣不太好...
    • 分解每個類別的責任
    • 決定是否讓新的類別曝光,如果不要,可考
    慮使用委託方法(delegation method)

    View full-size slide

  79. 自我封裝欄位
    Self Encapsulate Field
    • 使用 getter/setter 來取用實體變數
    • Lazy initialization(要用的時候才初始化)

    View full-size slide

  80. class Price
    def total
    @base_price * (1 + @tax_rate)
    end
    end
    使⽤用前

    View full-size slide

  81. class Price
    attr_reader :base_price, :tax_rate
    def total
    base_price * (1 + tax_rate)
    end
    end
    使⽤用後

    View full-size slide

  82. 重構:
    「在不改變程式碼外在行為的
    前提下,對程式碼做出修改,
    以改進程式的內部結構」

    View full-size slide

  83. Ruby 風味之程式整理手法

    View full-size slide

  84. dynamic method

    View full-size slide

  85. Dynamic Method 1/5

    View full-size slide

  86. DRY = Don’t Repeat Yourself

    View full-size slide

  87. Dynamic Method 2/5

    View full-size slide

  88. Dynamic Method 3/5

    View full-size slide

  89. Dynamic Method 4/5

    View full-size slide

  90. Dynamic Method 5/5

    View full-size slide

  91. Method Missing 1/3

    View full-size slide

  92. Method Missing 2/3

    View full-size slide

  93. Method Missing 3/3

    View full-size slide

  94. https://github.com/bbatsov/rubocop

    View full-size slide

  95. 高見龍 Blog
    Facebook
    Twitter
    Email
    Mobile
    https://kaochenlong.com
    https://www.facebook.com/eddiekao
    https://twitter.com/eddiekao
    [email protected]
    +886-928-617-687

    View full-size slide