iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

0
自我挑戰組

Metaprogramming Ruby and Rails系列 第 32

Ruby 學習筆記簿:Metaprogramming Quizzes

Quiz: Missing Lines

題目說明:

下面程式碼以圖表方式呈現,可看出物件與類別之間的關聯:

class MyClass
end

obj1 = MyClass.new
obj2 = MyClass.new

https://ithelp.ithome.com.tw/upload/images/20201025/20120868yX4mA39vUY.png

請回答以下問題:

1. What’s the class of Object? 答案: Class

2. What’s the superclass of Module? 答案: Object

3. What’s the class of Class? 答案: Class

4. 執行以下程式碼並畫出以上答案的關聯?

  obj3 = MyClass.new obj3.instance_variable_set('@x', 10)

答案

https://ithelp.ithome.com.tw/upload/images/20201025/20120868nLI0LAS6XG.png

Quiz: Tangle of Modules

題目說明:

請先看以下程式碼:

module Printable 
  def print
    "Printable#print"
  end

  def prepare_cover
    # ...
  end
end

module Document
  def print_to_screen
      prepare_cover
      format_for_screen
      print
  end

  def format_for_screen
    # ...
  end
  def print
    "Document#print"
  end
end

class Book
  include Document 
  include Printable 
  
  # ...
end

當我們建立了 Book 的實例物件 b,並呼叫 print_to_screen()方法。

b = Book.new
b.print_to_screen

假設這個時候問題出現了,因為輸出的字串是錯誤的,這代表呼叫了不對的 print()方法

請回答以下問題:

1. 請問我們到底呼叫了哪ㄧ個版本的 print()? Printable or Document?

2. 請在紙上畫出 ancestors chain

3. 請幫忙解 Bug,讓 print_to_screen() 可以呼叫正確的 print()

題目討論

首先我們可以請 Ruby 給點提示:

Book.ancestors # => [Book, Printable, Document, Object, Kernel, BasicObject]

從提示裡,我們得知 ancestor's chain 正確的面貌。在問題中並沒有特別說 Book 是繼承自哪個類別,因此如果沒有引入任何模組的話,在預設情況下,Book 是繼承自 Object。

但是當 Book 用 include 引入了 Document 模組,Ruby 會把 Document 模組加在 Book 的上面,也就成為 Book 的父層。接著又再 include 了 Printable 模組,Ruby 會再把 Printable 模組插入在 Book 的上面。此時的 ancestor's chain 的圖就會變成這樣:

https://ithelp.ithome.com.tw/upload/images/20201025/20120868qtcTZ2MP8T.png

還記得 Chapter 2 介紹的 Method Lookup 嗎?

當呼叫 b.print_to_screen 時,實例物件 b 會變成是當下執行的物件,也就是 self。接著依 『 黃金法則:往右ㄧ步,再往上 』 "One step to the right, then up",往右會先找到 Book 類別,再往上ㄧ直找到 Document#print_to_screen()。

進入 print_to_screen() 內,就準備開始執行裡面的方法。不過 print_to_screen()裡面的方法(包括 print)都沒有明確的接收者(receiver),因此 method lookup 只能再次從 Book 往上找。以 print() 方法來說,最接近 Book 的 print() 方法會在 Printable 模組找到。

要解決掉 Bugs 可以有兩種做法:

  1. 重新命名 Printable 模組裡的 print() 方法: 如此一來 method lookup 就只會在 Document 找到 print()。
  2. 把 Book 類別內引入模組的順序互換: 讓 Document 模組變成最接近 Book 就會先在 Document 找到 print()。

Quiz: Bug Hunt

題目說明:

請找出以下程式碼的錯誤並加以修正:

1     class Roulette
2       def method_missing(name, *args)
3         person = name.to_s.capitalize
4         3.times do
5           number = rand(10) + 1
6           puts "#{number}..."
7         end
8         "#{person} got a #{number}"
9       end
10    end

number_of = Roulette.new
puts number_of.bob
puts number_of.frank

正確的輸出應該是這樣:

#   5...
#   6...
#   10...
#   Bob got a 10
#   7...
#   4...
#   3...
#   Frank got a 3

題目討論

如果我們直接拿以上錯誤程式碼去跑跑看,會得到類似下面無限迴圈的錯誤訊息 (SystemStackError)。

找出這個錯誤訊息應該不算是太難,但是要了解錯誤發生的原因就不是那麼淺顯易見了。首先可以看出 number 變數是在 do...end 的 block 中定義的,然後會再傳給 times() 方法。

還記得在 Chapter 4 中提到 Scope 的概念,在block內定義的變數,其作用域只限於 block 裡。因此當程式跑到第8行的 number 時,Ruby 其實會認為 number 是方法,而不是在第 5 行所定義的變數。

假設我們在 Roulette 類別內定義的方法不是命名為 method_missing,其實錯誤訊息就是常見的名稱錯誤(NameError),但題目的陷阱或是有趣的地方就在於方法名稱是 method_missing

我們知道當 Ruby 找不到方法或是無法回應當物件時,就會呼叫 BasicObject 所內建 method_missing() 方法,因此程式跑到第 8 行的 number 時,會錯認為是方法,又在 self (Roulette的實例物件) 裡找不到 number 方法,所以呼叫 method_missing(),才造成了無窮迴圈的問題。

解決方案

分享自己快速但覺得不是很好的解法:設定 number 為全域變數,程式就不會錯認為第 8 行的 number 是方法了。

1     class Roulette
2       def method_missing(name, *args)
3         person = name.to_s.capitalize
4         3.times do
5           $number = rand(10) + 1
6           puts "#{$number}..."
7         end
8         "#{person} got a #{$number}"
9       end
10    end

書中提供的方法

1      class Roulette
2        def method_missing(name, *args)
3          person = name.to_s.capitalize
4          super unless %w[Bob Frank Bill].include? person
5
6          number = 0
7          3.times do
8            number = rand(10) + 1
9            puts "#{number}..."
10         end
11         "#{person} got a #{number}"
12        end
13      end

參考資料:

本篇所有題目都是來自於 Metaprogramming Ruby 2nd


上一篇
Ruby 學習筆記簿:Metaprogramming Workshop - Before Action
下一篇
Ruby 學習筆記簿:Metaprogramming Workshop - The Legacy System
系列文
Metaprogramming Ruby and Rails33

尚未有邦友留言

立即登入留言