--- title: Astro課程 0716 - Ruby (Day3) tags: astro, ruby --- [Git branch遊戲](https://learngitbranching.js.org/?locale=zh_TW) ## 時間軸 10:30 email正規表示 11:51 執行某個方法的搜尋路徑 13:49 測試 # OOP - `模組`和`繼承`有什麼不同? (想要會飛、但是不想繼承鳥類,直接掛一個會飛的模組) - 解釋多重繼承/鑽石繼承的好處及問題? (Eg. Python) # 模組 ## include ```ruby module Flyable def fly p "I can fly!" end end class Cat # include module name include Flyable end kitty = Cat.new kitty.fly ``` 往右看用`class`method, 往上看父類別用`superclass` 先往右找,找不到再往上找 ```ruby p kitty.class # Cat p kitty.class.superclass # Object p kitty.class.superclass.superclass # BasicObject p Object.class # Class p BasicObject.class # Class p Class.superclass # Module p Module.superclass # Object p Object.superclass # BasicObject p BasicObject.superclass # nil ``` - `Class`的父類別是誰? - `Module`的父類別是誰? - `Class`的類別是誰? - 所有`Class`的類別都指向誰? 所有`Class`的類別都指向`Class`,所以 ```ruby class Cat def eat end end ``` 等於 ```ruby Cat = Class.new { def eat end } ``` ## 執行某個方法的搜尋路徑 `.ancestors` ```ruby module F def fly puts "fly in module" end end class Animal def fly puts "fly in animal" end end class Cat < Animal include F end kitty = Cat.new kitty.fly p Cat.ancestors # =>[Cat, F, Animal, Object, Kernel, BasicObject] ``` ## 查詢Kernel和BasicObject的方法和數量 `.instance_methods` `Object`是冗員,只`include`Kernel和繼承`BasicObject`的方法 ``` 2.5.2 :002 > p BasicObject.instance_methods.count 8 => 8 2.5.2 :003 > p BasicObject.instance_methods [:!, :equal?, :instance_eval, :==, :instance_exec, :!=, :__id__, :__send__] => [:!, :equal?, :instance_eval, :==, :instance_exec, :!=, :__id__, :__send__] 2.5.2 :005 > p Kernel.instance_methods.count 50 => 50 2.5.2 :006 > p Object.instance_methods.count 58 => 58 ``` ## 查詢Class和Module的方法和數量 ``` 2.5.2 :008 > p Module.instance_methods.count 108 => 108 2.5.2 :009 > p Class.instance_methods.count 111 => 111 2.5.2 :011 > p Class.instance_methods(false) [:new, :allocate, :superclass] => [:new, :allocate, :superclass] ``` 用`Module`產生的模組少了`:new, :allocate, :superclass`三個方法,所以`沒有實體方法`,`不能繼承` ``` 2.5.2 :012 > m = Module.new => #<Module:0x00007f97268b6310> 2.5.2 :013 > m.new Traceback (most recent call last): 2: from /Users/tingtinghsu/.rvm/rubies/ruby-2.5.2/bin/irb:11:in `<main>' 1: from (irb):13 NoMethodError (undefined method `new' for #<Module:0x00007f97268b6310>) ``` ![](https://i.imgur.com/7MN9Vdu.png) [圖片來源](https://medium.com/lynn-%E7%9A%84%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98/rails-%E6%96%B0%E6%89%8B%E6%9D%91-ruby-object-model-5c3b4b869df6) ## extend 擴充 (只能用於擴充模組) ``` module F def fly p "I can fly" end end class Cat include F extend F end Cat.fly # extend: 類別方法 Cat.new.fly # include: 實體方法 ``` ``` module F def fly p "I can fly" end end a = "cc" a.extend(F) a.fly #=> "I can fly" ``` # 模組 namespace: 解決類別的同名問題 呼叫類別時,連名帶姓的呼叫`module::class` (Eg.三重金城武) ``` 模組::類別 class User < ActiveRecord::Base end class A module B end end kitty = A::B.new ``` Eg. ``` extend AA::BB ``` # 測試 TDD (Test-Driven Development)測試驅動開發 如何驗證程式碼可以正常運作? => 寫一段測試程式 如果之後改壞了,測試程式可以確保之前寫的正常運作 Single source of truth, SSOT (單一真相來源) - 測試本身就是規格 - 寫出更有信心的程式碼 - 未來可能需要重構 Eg. 簡單的測試 ``` def calc(x, y) x + y end # test 1 if calc(1, 2) == 3 puts "yes" else puts "no" end ``` ## 測試套件 minitest (語法難度,速度快) 與RSpec(好寫) ## 測試實作 Eg. ATM功能 存錢功能 - 可以存錢 - 不可以存0元或是小於0元的金額(越存錢越少) 領錢功能 - 可以領錢 - 不能領超過自己的存款 - 不可以領0元或是小於0元的金額(越領錢越多) ``` RSpec.describe ATM do end ``` `ATM`常數沒有定義的話,跑這個檔案會出錯 ``` ~/Documents/projects/astro_ruby   master  rspec atm.spec.rb An error occurred while loading ./atm.spec.rb. Failure/Error: RSpec.describe ATM do end NameError: uninitialized constant ATM # ./atm.spec.rb:1:in `<top (required)>' No examples found. Finished in 0.00006 seconds (files took 0.23286 seconds to load) 0 examples, 0 failures, 1 error occurred outside of examples ``` 先設常數:ATM=1 (測試通過不代表功能完整!) ``` ✘  ~/Documents/projects/astro_ruby   master  rspec atm.spec.rb No examples found. Finished in 0.0004 seconds (files took 0.38796 seconds to load) 0 examples, 0 failures ``` AAA測試原則: ``` Arange Act Assert ``` 硬寫看看: ``` RSpec.describe ATM do context "存錢功能" do it "可以存錢" do # AAA # Arrange t = ATM.new(10) # Act t.deposit(10) # Assertion expect(t.balance).to be 20 end end ``` 其他重點: - 實作部分使用`require './atm.rb'`引入檔案 - 改一個部分,看會RSpec不會壞(確保沒有改錯檔案) # 重構 重構的前提? (一邊重構、一邊測試) 重構的意義? 在不改變外部的情況下,調整裡面的結構,讓程式碼更容易閱讀 Eg. - `attr_reader` - 把變數改成容易理解的名稱 - 把未來可能會重複的程式賦予意義,寫成方法 ```ruby class ATM attr_reader :balance def initialize(balance) @balance = balance end def deposit(amount) if amount > 0 @balance += amount end end def withdraw(amount) if amount > 0 and is_enough?(amount) @balance -= amount end end private def is_enough?(amount) amount <= @balance end end ```