高見龍

iOS app/Ruby/Rails Developer & Instructor, 喜愛非主流的新玩具 :)

CoffeeScript 基本篇(一)

首先,CoffeeScript的官網的文件寫得相當的詳細,強烈建議各位一定要看,只要看完一輪,大概就知道CoffeeScript是怎麼一回事了。

底下你看到的console.log,通常是拿來debug用的,並不會真的在網頁上產生結果,請搭配Firebug使用。

註解

CoffeeScript跟Ruby一樣,使用#字號來做註解,而且註解的內容在編譯過的js檔裡是看不到的。如果要做多行註解,則是使用3個#字號。

變數宣告

其實變數不用特別宣告,直接抓來用就好了,例如:

1
2
age = 18;
name = "CoffeeScript";

編譯結果:

1
2
3
var age, name;
age = 18;
name = "CoffeeScript";

看到了嗎? 它幫你宣告好了agename兩個變數,而且該加的分號都加上去了。

如果你要組合字串,除了用加號來串接外,CoffeeScript也從Ruby那邊借來了string interpolation:

1
greeting = "hello, #{name}"

編譯結果:

1
greeting = "hello, " + name;

要注意的是,如果你用的是單引號的話,string interpolation不會發生作用。

跟Ruby一樣,string interpolation可以放一個完整的expression,例如:

1
2
3
4
5
6
greeting = "hello, #{
  if name
      name
  else
      "CoffeeScript"
}"

編譯結果:

1
2
var greeting;
greeting = "hello, " + (name ? name : "CoffeeScript");

Function

1
2
3
4
say_hello = ->
  "Hello CoffeeScript"

say_hello()

編譯結果:

1
2
3
4
5
var say_hello;
say_hello = function() {
  return "Hello CoffeeScript";
};
say_hello();

像Ruby一樣,function的最後一個執行結果自動就是整個function的回傳值,所以這邊即使沒有特別寫return,該加上的return、大括號以及分號,都在編譯成js檔之後加上去了。(你要特別加上return也ok,不會變兩個return的)

再來,如果你要給function傳進參數的話:

1
2
3
4
5
6
7
8
9
10
11
12
age = 18;
name = "CoffeeScript";
greeting = "hello, #{name}"
say_hello = (name) ->
if name
  "hello, #{name}"
else
  greeting

console.log say_hello()
console.log say_hello("eddie")
console.log say_hello "eddie"

編譯結果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var age, greeting, name, say_hello;
age = 18;
name = "CoffeeScript";
greeting = "hello, " + name;
say_hello = function(name) {
  if (name) {
      return "hello, " + name;
  } else {
      return greeting;
  }
};
console.log(say_hello());
console.log(say_hello("eddie"));
console.log(say_hello("eddie"));

這樣就可以傳參數進去了。

上面這段範例有兩件事要特別留意,首先,CoffeeScript像Python一樣使用縮排(identation)做為程式碼區塊的判斷,所以上面if..else..的縮排是有意義的。是的,if後面不用加then

第二,注意到在上面傳參數的時候,像Ruby一樣的省略掉了小括號嗎? 編譯出來的結果跟有小括號的是一樣的。那為什麼say_hello不傳參數的時候不能像Ruby一樣省略參數? 我想是因為CoffeeScript沒辦法真的像Ruby一樣辨識say_hello到底是一般的變數,還是沒有參數的方法呼叫,所以請記得,如果沒傳參數給function,請記得加上小括號

如果你要讓參數有預設值:

1
2
say_hello = (name = "CoffeeScript") ->
  console.log "Hello, #{name}"

編譯結果:

1
2
3
4
5
6
7
var say_hello;
say_hello = function(name) {
  if (name == null) {
      name = "CoffeeScript";
  }
  return console.log("Hello, " + name);
};

在Ruby裡傳參數,有叫做Splat的概念,可以一次傳進多個選擇性的參數,在CoffeeScript也有借來用,只是寫法有點不太一樣:

1
2
3
4
5
6
7
8
9
say_hello = (name, others...) ->
  if others.length > 0
      "Hello, #{name} and #{others}"
  else
      "Hello, #{name}"

console.log say_hello "eddie"
console.log say_hello "eddie", "kao"
console.log say_hello "eddie", "kao", "joanne"

編譯結果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var say_hello;
var __slice = Array.prototype.slice;
say_hello = function() {
  var name, others;
  name = arguments[0], others = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
  if (others.length > 0) {
      return "Hello, " + name + " and " + others;
  } else {
      return "Hello, " + name;
  }
};

console.log(say_hello("eddie"));
console.log(say_hello("eddie", "kao"));
console.log(say_hello("eddie", "kao", "joanne"));

第二個參數others後面接三個點,表示它是一個「可有可無而且可以不定數量」的參數,而傳進來的參數變成一個陣列,看到編譯出來的程式碼就覺得如果要自己手工寫出這段程式碼要想好一下子的..

像Ruby一樣,CoffeeScript支援變數多重指定,但寫法有些不一樣,需要特別加上中括號,所以可以這樣玩:

1
2
3
4
[first, others..., last] = [1..10]
console.log first
console.log last
console.log others

編譯結果:

1
2
3
4
5
6
var first, last, others, _i, _ref;
var __slice = Array.prototype.slice;
_ref = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], first = _ref[0], others = 3 <= _ref.length ? __slice.call(_ref, 1, _i = _ref.length - 1) : (_i = 1, []), last = _ref[_i++];
console.log(first);     # 1
console.log(last);      # 10
console.log(others);    # [ 2, 3, 4, 5, 6, 7, 8, 9 ]

變數可以多重指派,當然也可以交換:

1
2
3
4
5
6
x = 1
y = 10
[x, y] = [y, x]

console.log x
console.log y

編譯結果:

1
2
3
4
5
6
var x, y, _ref;
x = 1;
y = 10;
_ref = [y, x], x = _ref[0], y = _ref[1];
console.log(x);
console.log(y);

可以用原來的方式寫function嗎?

你不一定要照著CoffeeScript的方式寫,如果CoffeeScript的語法讓你覺得不舒服,你希望可以用你習慣的JavaScript語法的話也ok,你可以這樣做:

1
2
3
say_hello = `function(name){
  return "Hello, " + name
}`

在反單引號(backtick)包起來的內容,在編譯的時候會照你寫的內容輸出,只是這樣一來用CoffeeSCript就沒意義啦!

陣列

1
heroes = ['Spider Man', 'Captain America', 'X-men', 'Iron Man']

在CoffeeScript裡,你可以用多行表示:

1
2
3
4
5
6
heroes = [
  'Spider Man',
  'Captain America',
  'X-men',
  'Iron Man'
]

編譯結果:

1
2
var heroes;
heroes = ['Spider Man', 'Captain America', 'X-men', 'Iron Man'];

甚至連每行後面的逗點也都可以省下來。

CoffeeScript從Ruby借來了Range

1
students = [1..10]

編譯結果:

1
2
var students;
students = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

如果是多一個點:

1
students = [1...10]

編譯結果:

1
2
var students;
students = [1, 2, 3, 4, 5, 6, 7, 8, 9];

如果你把Range反過來寫,例如[10..1],它就會變成從10到1的數字陣列。但如果把Range的數字放大一點:

1
students = [1..100]

編譯結果:

1
2
3
4
5
6
var students, _i, _results;
students = (function() {
  _results = [];
  for (_i = 1; _i <= 100; _i++){ _results.push(_i); }
  return _results;
}).apply(this, arguments);

只要Range大於20就會改用迴圈的方式來建構這個陣列。

陣列的操作也是很容易的

1
heroes[0..2]

編譯結果:

1
heroes.slice(0, 3);

如果利用Range來替換或新增陣列的內容:

1
heroes[1..2] = ["Batman", "ThunderCat"]

編譯結果:

1
2
3
var _ref;
heroes = ['Spider Man', 'Captain America', 'X-men', 'Iron Man'];
[].splice.apply(heroes, [1, 2].concat(_ref = ["Batman", "ThunderCat"])), _ref;

物件

你可以用這種JSON的寫法:

1
eddie = { name: "Eddie Kao", age: 18, speciality: "eat" }

或是YAML式的寫法:

1
2
3
4
eddie =
  name: "Eddie Kao"
  age: 18
  speciality: "eat"

編譯結果:

1
2
3
4
5
6
var eddie;
eddie = {
  name: "Eddie Kao",
  age: 18,
  speciality: "eat"
};

特別是如果你要做巢狀的物件結構,用YAML的寫法會更清楚:

1
2
3
4
5
6
7
8
9
10
11
eddie =
  name: "Eddie Kao"
  age: 18
  speciality: "eat"
  lovers:
  nayumi:
      name: "Nayumi Hung"
      age: 18
  mary:
      name: "Mary Bloody"
      age: 20

CoffeeScript 使用篇

CoffeeScript裝好了,準備來泡咖啡了

假設我寫了一個檔名為app.coffee(內容怎麼寫請見CoffeeScript基礎篇),現在我想來編譯這個coffee檔:

> coffee --watch --compile app.coffee

這行指令會把app.coffee編譯產生一個同名的app.js

加上了–watch參數,CoffeeScript的編譯器會一直監看這個檔案,只要檔案一有變動,編譯器就會再重新編譯產生一份新的.js檔案。如果你覺得–watch、–compile太囉嗦或不想打那麼多字,也可以用-w、-c來替代。

如果你希望把幾個coffee檔案在編譯的時候合併成一個.js檔,以減少http request的數量,你可以這樣做:

> coffee --join application.js --compile *.coffee

這樣就會把同一個目錄裡的coffee檔編譯並組合成同一個js檔,這裡–join也可以簡寫成-j。

更多內容請見coffee –help

CoffeeScript Console

CoffeeScript也有像Ruby的irb一樣的REPL,提供一個像shell的互動介面可以讓你試玩,你只要不帶參數的執行CoffeeScript編譯器:

> coffee
coffee> name = "eddie"
'eddie'

這樣你就可以在這個環境裡面試一些語法。如果你曾經使用過Ruby的irb,你對這個結果應該不陌生。

一定要編譯成js檔嗎?

上面講的是如何把coffee檔編譯成js檔,編譯成js檔當然放到網頁上當然沒問題,但是不是一定要編譯呢? 不編譯的話,coffee檔能不能直接拿來用?

當然是可以的,只要先引入一行CoffeeScript的編譯器,就可以在HTML的頁面裡直接寫CoffeeScript:

1
<script type="text/javascript" src="http://jashkenas.github.com/coffee-script/extras/coffee-script.js"></script>

然後你要這樣寫:

1
2
3
<script type="text/coffeescript">
  alert 'test'
</script>

或是寫在外部檔案:

1
<script type="text/coffeescript" src="myapp.coffee"></script>

都可以,這樣就能讓你的瀏覽器就看得懂CoffeeScript,只是這樣一來的速度一定比直接編譯成.js檔要來得慢,所以實務上還是會先編譯成.js檔再拿來使用。

OK! 可以開始喝咖啡了!

CoffeeScript 安裝篇

要安裝CoffeeScript的話,會需要先安裝Node.js以及Node.js的套件管理工具 – npm (Node Package Manager)

說明:

以下將會以Mac及Ubuntu為主要的平台,Windows就對不起了,我沒有機器可以測試..在安裝過程中如果出現權限不足的錯誤訊息,請使用sudo暫時取得安裝的權限。

安裝Node.js

官網:http://nodejs.org/

> git clone git://github.com/joyent/node.git
> cd node
> ./configure
> make
> sudo make install

更多細節請參考:https://github.com/joyent/node/wiki/Installation

安裝npm

官網:http://npmjs.org/

> curl http://npmjs.org/install.sh | sh

一行搞定!(如果你沒有curl的話請另外安裝)

安裝CoffeeScript

如果你的Node.js以及npm都順利安裝完成的話,接下來,在安裝之前先更新一下npm的套件資訊(不一定更新,只是確保npm會幫你裝最新的套件版本):

> npm update
npm info it worked if it ends with ok
npm info using npm@0.3.18
npm info using node@v0.4.10
npm WARN Invalid range in engines.node.  Please see `npm help json` ~v0.4.9
npm WARN Invalid range in engines.node.  Please see `npm help json` v0.4.8~v0.4.9
npm info outdated Everything up-to-date.
npm info update Nothing to update
npm ok

再來安裝CoffeeScript:

> npm install coffee-script
npm info it worked if it ends with ok
npm info using npm@0.3.18
npm info using node@v0.4.10
npm info preinstall coffee-script@1.1.1
npm info install coffee-script@1.1.1
npm info postinstall coffee-script@1.1.1
npm info preactivate coffee-script@1.1.1
npm info activate coffee-script@1.1.1
npm info postactivate coffee-script@1.1.1
npm info build Success: coffee-script@1.1.1
npm ok

其實如果你是Mac的使用者的話,上面這些動作可以直接用homebrew一行搞定,超幸福的:

> brew install coffee-script

homebrew會幫你把需要的東西一起處理好(誠心推薦,不要再用ports了)。如果你發現用homebrew安裝的CoffeeScript不是最新版本的話,請先用brew update指令,更新一下homebrew的fomula再重新安裝即可。

看看有沒裝好:

> coffee -v
CoffeeScript version 1.1.1

就這樣,可以準備開工了!

補充:

在 Windows 上可以用這個 https://github.com/alisey/CoffeeScript-Compiler-for-Windows

在 NetBeans 上可以用這個 https://github.com/dstepanov/coffeescript-netbeans (注意, Windows 7 要用 0.9.1 版才不會有問題)

感謝jace補充 :)

CoffeeScript 簡介

什麼是CoffeeScript?

簡單的說,它就是JavaScript啦,只是用不同的程式語言來寫而已。

為什麼選擇CoffeeScript?

不少JavaScript社群,包括JavaScript的老爸Brendan Eich,也對它讚譽有加,有社群及大神的背書,加上Rails 3.1之後會把CoffeeScript變成預設支援,是我選擇它的原因之一。不過對我個人來說,CoffeeScript的語法感覺像是PythonRuby的混合體,從這些語言身上借了一些我個人很愛的元素,加上對我個人來說學習的負擔不算太大,這才是我選擇它最主要的原因(聽說CoffeeScript還有向Haskell取經,不過我跟它不熟)。

從Python借來的:

  • 使用縮排(identation)來取代大括號,這一直是我的菜,我有程式碼潔癖!
  • 簡單好用的List Comprehension語法,在CoffeeScript也可以用(雖然用起來有那麼一點點不太一樣)。

從Ruby借來的:

  • 有if modifier可以用,可以把if寫在句子後面,也有unless可以用。
  • 在呼叫方法以及邏輯判斷的時候,可以視情況省略小括號。
  • 每行行尾不用分號(其實Python也是)。
  • 不用特別寫return,最後的執行結果就是回傳值。
  • 有類似irb的console介面模式可以用。

優缺點

好處?

  • 程式碼會變短,變得容易閱讀,也更容易抓出問題來,即使是以OOP的方式來寫js,語法看起來也是相當清楚。
  • 經過CoffeeScript編譯出來的JavaScript,因為它在編譯的時候有做最佳化,而且這些最佳化可能是我們這種小兵不知道的技巧,某些情況下的編譯出來的效能可能比我們一般手寫的要來得好。
  • 不會有因為變數scope而混淆程式語意或造成或memory leak的問題。
  • 編出來的js檔丟給JSLint檢查不會跳出一堆抱怨。

缺點?

  • 你可能得再花時間再熟悉一門程式語言,不過如果你本來就熟悉Python或Ruby的話,其實負擔並不大,而且老實說Python/Ruby也沒很難學。

FAQ

Q: 學CoffeeScript就可以取代一般的JavaScript嗎?

A: 也許可以取代,但這不代表你就不用學JavaScript,如果你並不熟悉JavaScript是怎麼一回事,你應該也不知道你要拿CoffeeScript來取代什麼東西。最一開始也提到,CoffeeScript就只是JavaScript而已,所以原本該花時間在學習JavaScript的,不會因為CoffeeScript而省下來的。

Q: CoffeeScript聽說要編譯成*.js檔才能用?

A: 是的。但如果你不想編譯,透過在瀏覽器裡直接引入compiler也可以讓瀏覽器看得懂*.coffee,只是通常實務上還是會把編成*.js檔。BTW, CoffeeScript的compiler本身也是用CoffeeScript寫的,自己寫自己,這點光是聽起來就很酷。

Q: 改用CoffeeScript的話,是不是之前寫的JavaScript都得整個打掉重寫?

A: 也不用啦,CoffeeScript的目的並不是取代原來的JavaScript,而是用更簡單、清楚的語法來寫JavaScript,所以原來寫好的就留著吧,除非你也覺得寫得不好,加上手癢想練功。而且如果你想在CoffeeScript裡直接使用一般JavaScript的語法也是ok的,CoffeeScript可以讓你把一般的JavaScript語法embed在CoffeeScript裡。

Q: CoffeeScript可以把.coffee檔編譯成.js檔,那也有工具可以把js檔轉成coffee檔的嗎?

A: 有的,有個工具叫做js2coffee,你可以在線上玩看看,也可以透過npm安裝到你的電腦裡。

Q: 有開發工具嗎?

A: CoffeeScript的官網有列了一些工具供參考。不過寫CoffeeScript只要一般的文字編輯器就行了,如果你沒有特別偏好的文字編輯器,我推薦你使用vim,它是個免費又功能強大的文字編輯器,投資時間學一下它保證不會後悔的。vim有一堆很威的plugin可以用,我從pct那邊fork了他的vim設定,也把CoffeeScript的外掛加上去了 https://github.com/kaochenlong/pct.vim (PS: 其實pct的版本就有加上這功能了..)

如果你是用Textmate,恭喜你CoffeeScript的作者有放出了個方便的bundle可以用。除了一些方便的snippet以及程式碼顏色亮度之外,還可以讓你很簡單的執行/檢視你的coffee檔到時候編譯成js檔的樣子。

Microsoft的Visual Studio 2010最近也有一個免費的web workbench套件可以用,讓VS也開始支援SassLess以及CoffeeScript

Q: 有書可以參考嗎?

A: 在這個當下,剛好有一本”CoffeeScript: Accelerated JavaScript Development“,不過我想官網的資料相當完整,看完你就差不多知道怎麼用了。

另外還有一些不錯的學習資源:

Public, Protected and Private Method in Ruby

如果你曾經在別的程式語言寫過OOP,你也許對類別的方法存取限制不會太陌生。類別的方法的存取限制常見的有三種:public、protected以及private。

這三種存取限制,比較常聽到的解釋大概會是像這樣:

“public就是所有的人都可以直接存取,private是只有在類別內部才可以存取;而protected差不多是在這兩者之間,比private寬鬆一些,但又沒有public那麼自在,protected在同一個類別內或是同一個package,或是繼承它的子類別可以自由取用,但如果不是的話則不可存取。”

Ruby也有類似的方法存取限制,為什麼特別說「類似」,前面又為什麼需要特別提「大家常聽到的解釋」,因為Ruby在這部份的實作是不太一樣的,待會後面會再詳細說明。

怎麼做?

先來看看怎麼寫,Ruby的方法存取限制有兩種寫法,一種是寫在方法定義之前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Father

  def method_a
      puts "I am METHOD A in #{self.class}"
  end

  def method_b
      puts "I am METHOD B in #{self.class}"
  end

  protected
  def method_c
      puts "I am METHOD C in #{self.class}"
  end

  private
  def method_secret
      puts "I am spider man in #{self.class}"
  end

end

在Ruby的類別裡,方法只要沒有特別限制預設就是publilc的,除了一個例外,就是initialize方法,它永遠是private的,只會被new方法呼叫。

把存取控制放在前面的這種寫法,只要在它設定之後的方法定義都會受影響,除非又遇到另一個存取控制的設定。在上面的這段程式碼,method_a跟method_b沒有特別限制,所以是public方法(如果你想要特別加上public也沒問題,只是通常不會這麼做),method_c是protected方法,而method_secret則是屬於private方法。

另一種的方法存取限制是寫在方法定義之後:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Father

  def method_a
      puts "I am METHOD A in #{self.class}"
  end

  def method_b
      puts "I am METHOD B in #{self.class}"
  end

  def method_c
      puts "I am METHOD C in #{self.class}"
  end

  def method_secret
      puts "I am spider man in #{self.class}"
  end

  protected :method_c
  private :method_secret

end

哪種比較好?

這兩種寫法哪種方法比較好? 都好,隨個人喜好。我個人喜好第一種,因為我習慣會先把public的方法放在類別的上半部,而把private方法放在類別的最底下,所以使用第一種寫法對我來說寫起來比較順手。

其實public、protected以及private這三個在Ruby裡並不是關鍵字,它也只是Ruby裡的方法而已。

哪裡不一樣?

前面為什麼會特別提到Ruby的方法存取限制跟其它的程式語言「類似」呢? 雖然Ruby裡的確也有public、protected以及private,但事實上是不太一樣的,特別是private方法。我們先來看一小段的程式碼:

1
2
3
4
5
father = Father.new
father.method_a        # I am METHOD A in Father
father.method_b        # I am METHOD B in Father
father.method_c        # NoMethodError
father.method_secret   # NoMethodError

father是Father類別生出來的實體,而實體的public方法如所預期的印出結果,protected跟private方法呼叫的時候產生NoMethodError的例外,看起來很正常,那到底是哪邊不太一樣?

我們再來做個叫做Son的子類別,繼承自Father類別:

1
2
3
4
5
6
7
8
9
10
11
class Son < Father

  def son_method_c
      method_c
  end

  def son_method_secret
      method_secret
  end

end

我給Son類別加了兩個方法,分別會呼叫Father類別的protected跟private方法,再來看範例:

1
2
3
4
5
6
7
son = Son.new
son.method_a           # I am METHOD A in Son
son.method_b           # I am METHOD B in Son
son.method_c           # NoMethodError
son.method_secret      # NoMethodError
son.son_method_c       # I am METHOD C in Son
son.son_method_secret  # I am spider man in Son

在子類別呼叫父類別的protected方法,這不是新鮮事,但你注意到了嗎? 在子類別裡可以直呼叫父類別的private方法耶!

先來看這行:

1
son.method_a

一般我們會把這行翻譯成:

“有一個物件叫做son,然後呼叫了son物件的method_a方法”

不過如果你曾經認識過Smalltalk或是Objective-C的話,你會發現他們會把這行翻譯成:

“有一個接收者(receiver)叫做son,然後對著這個recevier送了一個叫做method_a的訊息(message)”

為什麼特別提這個? 因為在Ruby裡的private方法,只要沒有明確的指出recevier的話就都可以呼叫。所以在上面例子裡的Son類別,即使是呼叫父類別的private方法,只要不要有recevier,它就不會有錯誤產生。

也就是因為這樣,在Ruby的private方法其實不只類別自己內部可以存取,它的子類別也可以。再來看一下這段程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A

  def a
      self.b
  end

  private
  def b
      puts "Hello, This is Private B!"
  end

end

the_a = A.new
the_a.a        # NoMethodError

在A類別的a方法呼叫類別內部的b方法,看起來很合理吧,但實際執行就會出錯NoMethodError的例外,說你存取到了private方法了!! 為什麼? 因為你在呼叫方法b的時候加上了self,前面有提到”在呼叫Ruby的private方法時,不能明確的指定recevier”,在這裡卻明確的指出了self,所以出現錯誤訊息了。沒錯,連self也不行。

我們很常用的puts方法,它就是Object這個類別的private方法之一(更正確的說,是Kernel模組mixin到Object類別裡的方法)。我們平常會這樣用:

1
puts "Hello Ruby"

但如果你這樣做:

1
self.puts "Hello Ruby"

就會跳出NoMethodError

Protected方法?

那protected方法呢? 它的規定就沒那麼嚴格了,你要指定或不指定recevier都可以;至於public方法,就跟其它語言的定義差不多,隨便你用啦。

真的這麼private?

不過,其實Ruby的private方法也不是真的那麼private,轉個彎,一樣可以被外部呼叫:

1
2
3
father = Father.new
father.method_secret          # 是我們預期的 NoMethodError 沒錯
father.send(:method_secret)   # I am spider man in Father

前面提到的puts其實也可以改寫成:

1
Object.send(:puts, "Hello Ruby")  # Hello Ruby

這..這樣會不會有點扯? 如果連private都能被直接存取,那當初何必還要這樣設計呢? 還是直接乾脆全部都public就好了?

我想這其實是Ruby當初設計的哲學之一,Ruby把很大部份的權利都下放給程式設計師,讓開發者有最大的彈性空間可以運用(或惡搞),也就是這樣,在Ruby做Metaprogramming是相對的比在別的程式語言容易的。不只在這裡,你應該還可以在很多地方看到這個Ruby的專屬特性。

僅供參考

如果說這些存取限制只是”參考用”,那到底什麼時候會用到?

雖然說它只是”參考用”,我個人還是會把它當做是程式碼的寫作準則。雖然你可以透過send方法來存取private方法,但不代表你就應該這樣做。而且它也不是真的那麼沒有用,像是在寫Rails的時候,Controller裡的Action預設都是public的,如果你的routes.rb如果把路徑的對應全部打開,那所有的Action都有可能透過網址而被存取到,那也許不會是你想要的結果。