( 此系列文章用於記錄筆者在 Tealeaf Academy 學習 Rails 的心得和整理筆記,將不定期更新)

最近剛完成 Tealeaf Academy Track 1( Introduction to Ruby and Web Development ) 第一週的課程,這個 Track 主要是用來打穩學員的 Ruby 基礎,最後會做出一個網頁版的 Blackjack 遊戲。

雖然說是第一週的課程,但其實花了我兩週多的時間才完成,因為在上正式課程之前必須先熟讀 Tealeaf Academy 提供的 Ruby 教材(一本電子書),教材我覺得編的非常好,把初學者常搞混的 variable scope, pointers 等觀念拆解的很清楚,補足了我之前在 Treehouse 和 LRBTHW 沒學好的觀念。

第一週學員必須完成四個作業,分別是用 Ruby 寫出計算機、剪刀石頭布、圈圈叉叉和 Blackjack ,Tealeaf Academy 要求學員把作業上傳到 Github ,方便讓 TA 檢查作業。一開始做作業很辛苦,運行程式時常常出現一堆 error,但到後面就越來越有倒吃甘蔗的感覺,完成後非常的有成就感,有一種功力大增的感覺 XD

第一週的課程上完之後,筆者把自己覺得很重要或容易混淆的觀念做成筆記,希望能幫到大家。

一、 Ruby Style

Ruby Developer 會遵循一套特定的風格寫 code ,以便於閱讀並維持排版整齊,這個風格稱為”Ruby Style”,不論是 Ruby 初學者還是資深工程師都必須嚴格遵守這個風格。

Ruby Style 可歸類為五大原則:

(1) Tab  鍵設為縮進 2 spaces;indenting 設為使用 spaces。

以筆者用的 sublime text editor 為例,點開右下角的 Space 後必須設定成如下圖所示:

ruby style1

(2)當定義一個變數、方法或檔案時,必須遵循 “snake_case” 格式,原本的空白鍵會改為 _ 符號,且一律用小寫。

#Example 1 Benson Sun
benson_sun
	
#Example 2 Rails Gril Taipei
rails_girl_taipei

(3)當定義常數時(常數在整個程式中都不會被改變),以全大寫表示。

#Example 1 
FOUR = 4

#Example 2
NAME = "Benson"

(4)當使用 block時,多行用 do…end 表示,單行用{}表示。

 # Multi-line vs single line

  [1, 2, 3].each do |i|
    # do stuff
  end

  [1, 2, 3].each { |i| # do stuff }

(5)當宣告 class 時,使用 CamelCase 格式,移除空白鍵且第一個字全大寫。

#Class name example

class MyFirstClass
end

class MySecondClass
end

 

二、 Variable Scope(變數範疇)

variable scope 是決定變數能否被使用的重要關鍵,在 Ruby 中 variable scope 是被 block (程式區塊) 所決定的,block 通常用{ }或 do/end 來表示,但並不是所有的 do/end 都代表是一個 block,也就是說不一定所有的 do/end 都會產生 variable scope。

要精通 variable scope,必須記住一個原則

inner scope 可以使用定義於outer scope 的變數, 但 outer scope 不能使用定義於 inner scope的變數。

聽起來有點拗口,直接看一些例子會比較清楚:

x = 5           # 在 outer scope 被定義的變數

10.times do |n| # --> variable scope
  x = 3         # 在 inner scope 會被改變嗎?
end             # --> variable scope

puts x

答案是會被改變,x 最後會變成 3,因為外部的變數是可以被 inner scope 使用的。

再來看看第二個例子:

x = 5

10.times do |n| # --> variable scope
  x = 3
  y = 5         # 在 inner scope 被定義的常數
end             # --> variable scope   

puts x
puts y          # 可以在 outer scope 被使用嗎?

答案是不行,你會得到如以下的錯誤訊息:

undefined local variable or method `y'

這是因為在 inner scope 被定義的變數 y,在 inner scope 外就無法被使用。

再來看第三個例子,我們定義了一個方法叫作 some_method

x = 5

def some_method
  x = 3
end

some_method
puts x

x = some_method
puts x

這邊第一個的 puts x 還是 5,因為方法會產生一個完全獨立於程式執行過程的 scope,就算在這個方法中我們把 x 變成了 3 ,但 x 只有在方法內才會是 3,到了外面就又變回 5 了。若要改變 x ,必須在外部定義 x = some_method,第二個 puts x 結果是 3。

再來看第四個例子:

a = 1

if a == 1
  b =2     # b 在這裡被定義
end

puts b     # b 能夠在這裡被使用嗎?

答案是可以,因為 if / end 並不會產生 block ,它並沒有 { } 或 do/end ,自然不會產生 block,也不會產生 variable scope。

但我們剛剛一開始有提到,不是所有的{ }或 do/end 都會產生一個 block,那麼該怎麼去分辨呢?其實要決定是不是會產生 block,要看 { } 或 do/end 是不是緊接在 method 的調用( invocation )之後,如果是的話才會產生 block ,進而產生 variable scope,讓我們直接看看下面的例子:

arr = [1, 2, 3]

for i in arr do  #有 do 
  a = 5          # a 在這裡被定義
end              #有 end ,會產生 variable scope 嗎?

puts a           # a 能夠在這裡被使用嗎?

答案是可以,for …do/end 並不會產生 inner variable scope,因為 for..do/end 是 Ruby 語言的一部份,而不是方法的調用,像 each, times, loop 這些方法調用後面的 do/end 才會產生 block。

三、 Variables as Pointers

pointer 中文翻譯為指標,指標在維基百科的定義如下:

A pointer is a programming language object whose value refers directly to (or "points to") another value stored elsewhere in the computer memory using its address.

簡單來說,指標是一個物件,而這個物件的值是參考電腦中另一個記憶體位置的值來決定的,要瞭解這個概念,直接看例子會比較清楚:

a = [1, 2, 3]
b = a

a = [4, 5, 6]

puts b
a = [1, 2, 3]
b = a

a << 4

puts b

如果你實際運行這兩段 code ,結果可能會讓你很驚訝,在第一個例子當中,b 仍然是 [1, 2, 3],沒有隨著 a 值改變而改變;但在第二個例子中,b 卻隨著 a 的值而變成了 [1, 2, 3, 4],這是怎麼一回事?

要瞭解這兩段 code 的關鍵性差異,我們必須先了解變數其實是「指標」,換句話說,變數的本質上是一種物件,而這個物件會指向電腦的實體記憶體位置,然後讓這個記憶體位置儲存物件的值,在第一個例子當中,電腦實際上的運作情形是這樣的:

pointers

從上面的圖我們可以看到,當我們執行 a = [1, 2, 3, 4] 後 ,電腦會把 a 重新分配到完全不一樣的記憶體位置,這有點像是下錨的概念,每次我們執行 「=」這個指令時,電腦就幫我們把指定的值下錨在一個記憶體位置,讓記憶體位置來儲存這個值,當你用 「=」 指定另一個值的時候,電腦會再幫你下錨在另一個不一樣的位置。而且不同的記憶體空間也可以儲存相同的值,換句話說,如果我們再次指定 a = [1, 2, 3],即便 a 跟 b 的值是一樣的,但它們「下錨」的記憶體位置是不一樣的,值一樣只是碰巧而已。

再來看看第二個例子運作的情形:

pointers-2

在第二個例子中,「<<」這個指令並不會讓 a 重新指向另一個記憶體位置,相反的,它直接變更了該記憶體儲存的值,這個效果稱作「mutate the caller」,由於 b 跟 a 指向的是同一個記憶體位置,所以當記憶體儲存的值被修改時,b 自然也被改變了。

這邊我們可以導出一個結論--有些指令會 mutate the caller ,改變記憶體位置儲存的值,進而改變指向這個記憶體位置的變數,但有些指令則會把變數重新分配到新的記憶體位置

所有的變數類型,包括 arrays 和 hashes,都有方法( method )會 mutate the caller,當你呼叫這些方法時,它們不但會改變原本變數的值,也會改變所有指向這些記憶體位置的變數的值(就像第二個例子中的 b 一樣)。

所以,我們必須熟悉那些指令會 mutate the caller,哪些不會,一般而言,結尾帶有「!」的方法都會 mutate the caller,但其他像 pop、shift、unshift 等沒有「!」的方法也會,這沒什麼規則可循,只能用經驗來記憶了。

以上就是這週的筆記,下週的課程內容是 OOP (Object-Oriented Programming) 的架構觀念,大家下次見囉~

cover photo via Unsplash