---
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://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
```