Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts

2014/08/20

[廣告] Cocos2d-x實戰-手機遊戲策劃與開發的實務應用

嗨,我審校了一本書,在這裡打打廣告。

顧名思義,這本書的主題是Cocos2d-x這套開源遊戲引擎,內容有理論、有實例、也有營運建議,所以有「實戰」之名。

書名:Cocos2d-x實戰-手機遊戲策劃與開發的實務應用
作者:沈大海
審校:我
出版社:博碩
出版日期:2014年8月19日
頁數:576

Cocos2d-x遊戲引擎,因由開放原始碼、跨平台、高效能、安全性等等優良特點,受到全世界各地行動遊戲開發人員的熱愛,蘋果App Store排名前10名的遊戲中,有一半以上皆採用Cocos2d-x引擎,因此可知Cocos2d-x的魅力有多大。

本書從各個方向來介紹Cocos2d-x的理論基礎、核心技術、遊戲實例以及產品發布與市場營運的過程,另外還有C++程式設計、遊戲策劃、美術、等等,希望能夠幫助行動遊戲開發者快速瞭解並靈活運用Cocos2d-x引擎。書中的程式碼皆在Xcode和Cocos2d-x v2.2環境下通過編譯,並經過實際測試,具有非常高的實用價值,只要稍加修改便可用於商業化遊戲開發。

目錄

第1章 行動遊戲概論
介紹手機遊戲類型和開發平台的分析。

第2章 行動遊戲策劃和美術
介紹手機遊戲的策劃,包括分析玩家需求、編寫策劃文案、遊戲原型的實現。

第3章 行動遊戲程式設計概論
介紹C、C++語言的程式設計基礎,從實戰角度,透過實際範例讓讀者快速瞭解C和C++語言的語法特點。

第4章 Cocos2d-x介紹和開發環境設定
介紹Cocos2d-x的開發環境建置和Hello World專案實作,透過本章內容,您可以迅速入門開發一個最簡單的行動遊戲UI,並瞭解Cocos2d-x的運行原理。

第5章 Cocos2d-x核心技術
介紹Cocos2d-x核心技術,包括Cocos2d-x的核心類別CCDirector、CCScene、CCNode、CCLayer、CCSprite、CCAction等,並透過實例介紹Cocos2d-x的場景、圖層、互動、動作、動畫、粒子效果、遊戲地圖、物理引擎等。

第6章 Cocos2d-x遊戲範例:飛機大戰
以一款完整的遊戲專案《飛機大戰》來詳細解說手機遊戲的開發流程,並介紹遊戲進度和遊戲功能表的實作、遊戲按鍵處理與碰撞偵測等。

第7章 Cocos2d-x引擎二次封裝DeepBlueX
介紹DeepBlueX引擎,這是對Cocos2d-x的二次封裝,透過封裝讓開發者以狀態機的方式來規劃遊戲的整體架構,並介紹運用DeepBlueX引擎實作《打地鼠》。

第8章 Cocos2d-x網路程式設計
介紹Cocos2d-x網路程式設計與網路遊戲的實作,包括網路遊戲模型、使用Apache搭建網路遊戲伺服器端、使用Cocos2d-x實作HTTP網路連接、使用BSD Socket實作Socket傳輸通訊。

第9章 手機遊戲的營運和推廣
介紹遊戲打包與發布,包括發布遊戲應準備的專案、製作遊戲介紹文檔和圖示,以及如何尋找發布通道。

2014/06/30

[廣告] Windows 8.x App市集應用程式開發-使用 C# 與 XAML

嗨,我翻譯了一本書,在這裡打打廣告。

顧名思義,這本書的主題是在Windows 8平台上,運用C#與XAML來開發App;作者是微軟開發技術傳教士。

Windows軟體市集裡有著各式各樣豐富的App,諸如生產力工具、遊戲以及各種社群軟體p,直接提供給數以百萬計已經在使用Windows 8.x或Surface的人們;Windows市集應用程式擁有許多引以為傲的新特色,包括觸控螢幕與觸控筆輸入介面、程式之間的標準傳輸管道,以及能與網路網站緊密的結合,還有更棒的是,打造Windows市集應用程式時,你能運用原先早已熟悉的工具:C#與XAML。

本書將向擁有傳統桌面程式開發經驗的讀者,介紹最新的Windows 8.x App開發技術,以好幾支精心設計的範例,講解Windows各項特色、Windows Runtime、以及程式設計的最佳實務慣例,在閱讀本書的旅途中,你也將學習部署軟體的技巧,包括上架至Windows市集銷售到全世界。

目錄

第1章:哈囉,Windows新一代Modern介面
第2章:Modern UI
第3章:Windows Runtime與.NET
第4章:XAML
第5章:版面配置
第6章:儀表板
第7章:筆刷、圖形、樣式、資源
第8章:顯示漂亮的文字
第9章:控制項、繫結與MVVM
第10章:檢視控制項、語義式縮放與導覽
第11章:應用程式列
第12章:啟動顯示畫面、APP 標題、通知
第13章:檢視狀態
第14章:協定:和平共處
第15章:檔案
第16章:處處非同步
第17章:SOAP與RESTful網路服務
第18章:以socket開發聊天軟體
第19章:一點點UI作業:使用者控制項與Blend
第20章:透過網路傳送玩家位置
第21章:鍵盤、滑鼠、觸控螢幕、加速度感測器與遊戲手把
第22章:應用程式設定與暫停/回復
第23章:部署與販售應用軟體

2011/09/07

寫程式用的字型

2019.11.14更新:我在寫這篇文章時,並沒有考慮字型的授權問題,如商業使用是否要錢,可否在公司內使用。感謝熱心網友Karen Ferrer提供另一篇分享文70+ Best Free Fonts for Designers – Free for Commercial Use in 2019,可找到能用於商業的免費字型,等寬字型在第9節。


這裡列出一些等寬字型(fixed-width or monospaced fonts),主要用於撰寫程式時,以及終端機命令列模式下使用。喜歡與否,見仁見智,大家試試看,自己看的順眼最重要。

挑選時,有些注意事項:

  • 英文字母大寫O、小寫o、數字0,必須能夠清楚辨識。
  • 英文字母小寫l,大寫I、小寫i,數字1、符號|,必須能夠清楚辨識。 
  • 符號`與',必須能夠清楚辨識。
  • 字型的筆劃粗細適中。
  • 有些字型,設定在某種大小下時,非常漂亮,但可能在放大或縮小後,卻變差了。
現在作業系統各有其獨特的文字描繪技術,同一套字型在不同編輯器下看起來不盡相同,在我的環境下可能很棒,但在你那邊可能差強人意,更何況,每個人的感覺不同、偏好不同,所以請自己實際用用看,要注意各種相關參數的設定,譬如anti-aliasing、ClearType、等等。


Courier、Courier New
由IBM根據打字機的字型所設計的,因為沒有維護其專利,所以流傳廣泛。
我的感覺:Courier太難看。Courier New中規中矩,筆劃有點細。
下載:各系統上應該都有安裝此字型;Courier New可到這裡找找。

Andale Mono 
很多系統預設安裝的字型。
我的感覺:比Courier New好,但有更棒的。
下載:請到這裡。

Monaco、Menlo
Monaco是Apple的Mac作業系統內建的終端機預設字型,在Mac OS X 10.6被Menlo取代。Menlo是由Bitstream Vera Sans Mono改過來的。
我的感覺:不錯,在Mac上嫌麻煩的話就直接用這兩個字型吧。
下載:據我所知,只能在Mac OS上合法使用。

Source Code Pro
Adobe釋出的等寬字型,很不錯。
我的感覺:很不錯。
下載:請到這裡。

ProFont
我的感覺:不錯,推薦。
下載:請到這裡:

Bitstream Vera Sans Mono
我的感覺:整體看起來筆劃夠黑,我喜歡,但是符號*的位置偏上,我不喜歡。
下載:請到這裡。

Deja Vu Sans Mono
由Bitstream Vera Sans Mono改過來的,免費。
我的感覺:很不錯,推薦。整體看起來筆劃夠黑,我喜歡,但是符號*的位置偏上,我不喜歡。
下載:請到這裡。

Monofur
我的感覺:有點古老的味道。
下載:請到這裡。

Inconsolata
我的感覺:很不錯,推薦。單引號與雙引號有點卷曲歪斜,有人不喜歡所以繼續修改,變成Inconsolata-g。
下載:請到這裡。

Anonymous Pro
Mark Simonson設計,免費。
我的感覺:很不錯,推薦。
下載:請到這裡。


參考資料:
  1. Top 10 Programming Fonts
  2. Fixed-width fonts
  3. Monospace/Fixed Width Programmer's Fonts
  4. Font Survey: 42 of the Best Monospaced Programming Fonts
  5. 5 good programming fonts 

2011/08/23

MoonScript幾支小程式

這篇要寫幾支MoonScript程式。

關於MoonScript的概觀介紹請看這篇,如何安裝看這篇,完整的MoonScript語言參考手冊在這裡(英文)。

注意:我假設你已經會Lua了。

首先,向世界說聲哈囉吧。

寫一支沒有參數的函式並呼叫它。

func_hello = -> print "hello world"
func_hello!
func_hello()

輸出:
hello world
hello world

用->(箭頭)來產生函式,用=(等號)把產生出來的函式指派給func_hello,無參數的函式,除了可以用一般的()括號來呼叫,也可以用!(驚嘆號)來呼叫。而且這支函式的內容只有一個述句(statement),所以直接寫在->的後面即可。

如果函式內容多於一個述句,要縮排後一行一行放在->之下。

func_hi = ->
  name = "John Smith"
  print "Hi, I am " .. name

接下來,有參數的函式,要把參數宣告在->之前,以,(逗號)分開,以()括號包起來。

func_add = (x, y) -> return x + y
func_add2 = (x, y) -> x + y
print(func_add(1, 2))
print(func_add2(3, 4))

輸出:
3
7

func_add有return,func_add2沒有,但兩者是一樣的,這叫做implicit return,函式執行時最後的運算式,會回傳給呼叫者。

函式可以回傳多個值。

power_of_2_3 = (x) -> x*x, x*x*x
a, b = power_of_2_3 5
print a
print b

輸出:
25
125

函式呼叫可省略掉括號。

print(func_add(7, 8))
print func_add 7, 8

輸出:
15
15

省略括號後,參數會被傳進離它最近的函式,所以上面的7跟8會被傳進func_add。

當情況比較複雜會搞混時,當你想控制誰是誰的參數時,就用()括號來區分。

print "9 + 10 =", func_add(9, 10), "11 + 12 =", func_add(11, 12)


可以讓參數有預設值。

func_inc = (a, b=1) -> a + b
print func_inc 10, 5
print func_inc 10

輸出:
15
11

參數預設值的運算式,會一個跟著一個被執行,所以,後面參數可以使用前面參數。

foo = (x=100, y=x+5) -> x + y
print foo!
print foo 95

輸出:
205
195

接下來,介紹非常好用的table comprehension。過去,第一次看到Python的list comprehension時, 就覺得這玩意兒怎麼這麼強啊,真是太好用了,程式碼會變得更簡短,光一行就能完成超多事情。comprehension就好像把資料結構、迴圈、條件判斷 式通通攪和在一起,是個高階的程式概念,底下,就讓我用MoonScript來練習寫幾支table comprehension的程式吧。

若scores裡面是學生的成績,考的太差了,只好開根號乘以10,調整一下成績,以免當太多人,下次開課會教室不夠大。

scores = {64, 33, 25, 38, 48}

scores_modified = [math.sqrt(s) * 10 for i, s in ipairs scores]

print s - s%1 for i, s in ipairs scores_modified

首先,宣告變數scores,用{}大括號產生出table,裡面放了五個人的分數,在這裡,我們把table當做array或list來用,也就是說,裡面的項目是逐一排列的。然後,我們要把這個table裡面的項目,一個一個拿出來,也就是for i, s in ipairs scores這段所做的事情,i會是index,從1到5,在這裡沒有用到,s會是那五個成績,拿到成績後,開根號乘以10,算出調整後的成績,然後用[]方括號包起來,這就是comprehension,結果會是一個table,指派給scores_modified。

然後,用迴圈輸出成績,其中s - s%1是取出整數的部分,小數我們就不要了。

讓我們算一算調整前後各有幾個人被當。

stemp = [s for s in *scores_modified when s < 60]

print #stemp

同樣使用comprehension,因為逐一取出項目這個動作很常見,所以MoonScript加入一個語法,請看for s in *scores_modified,這個*星號,會等於for i, s in ipairs scores_modified,還有,我們用when來篩選出想要的項目,在這裡是小於60的成績,也就是不及格的,然後把結果放進table stemp裡。

#井號接table,表示table的大小,不過,這只有在我們把table當做array來用時,這個運算子#才會得到正確的結果。

執行看看吧,調整成績後,還有多少人會被當呢?

接下來,MoonScript與Lua有個不一樣的地方,就是if與for不僅是statement,也是expression,意思是說,它們都能回傳值。先看看if。

寫一個函式,給定一個成績參數,判斷是否過關或被當,第二個參數可有可無,若有,表示過關的最低成績,預設值是60。

pass_or_fail = (score, score_to_pass = 60) -> score >= score_to_pass

result = ""
if pass_or_fail 60
  result = "Pass"
else
  result = "Fail"

result = if pass_or_fail 60
  "Pass"
else
  "Fail"

如果if只是statement,不是expression的話,我們就必須要用上半部那種寫法;但MoonScript是expression,所以就能用下半部那種寫法。這兩種寫法的結果result會是相同的。

接下來看看for可以怎麼用。假設從1到10,奇數就保留,偶數就乘以2,怎麼寫呢?

doubled_evens = {}
for i=1, 10
  if i % 2 == 0
    doubled_evens[i] = i * 2
  else
    doubled_evens[i] = i

doubled_evens = for i=1, 10
  if i % 2 == 0
    i * 2
  else
    i

如果for只是statement,那就要用上半部的寫法,但MoonScript的for也是expression,所以可以用下半部的寫法,for每次迴圈的值,會被收集起來,放進table裡,最後被指配給doubled_evens。所以,這兩種寫法的結果相同。

另外,雖然Lua也可以寫OOP,但沒有直接的語法支援,總是會有礙手礙腳的地方,MoonScript加入了class、inheritance等支援,請你自己看看吧。

2011/08/22

MoonScript概觀介紹

這一篇把MoonScript的首頁,概略翻譯出來。

MoonScript是個動態腳本語言,以CoffeeScript的語法為基礎,不過,編譯後的語言是Lua。

成功安裝MoonScript後,你會得到:兩支執行檔moon與moonc,以及Lua模組moonscript。使用moon可以直接執行一支MoonScript程式檔;使用moonc可以把MoonScript程式檔編譯轉成Lua程式檔,之後再執行;在Lua程式裡加上require "moonscript",就能看懂、載入、執行MoonScript檔案了。

因為編譯後的結果是Lua程式碼,所以相容於各種Lua實作,包括LuaJIT,也相容於所有已經寫好的的Lua程式庫。

完整語言的介紹,請見MoonScript語言參考手冊(英文),我也寫了幾支小小的程式。接下來要概略介紹一下,因為我們可把MoonScript想像成為Lua披上並擴充較好看的語法外衣,所以你需要對Lua具有一定程度的了解。


概觀

MoonScript提供簡潔的語法,利用縮排來判斷並分割程式碼的各部分,而不是像Lua一樣使用囉嗦的關鍵字,也不是像C語言一樣使用大括號,利用縮排來定義語法的程式語言,有名的有Python。底下是MoonScript的一些述句構成。

export my_func
x = 2323

collection =
   height: 32434
   hats: {"tophat", "bball", "bowler"}

my_func = (a) -> x + a

print my_func 100

其了較簡潔的語法外,還加入了其他特色,包括table comprehensions、函式裡的implicit return、類別(class)、繼承(inheritance)、scope的管理述句import與export、以及很便利的物件建構述句with。

import concat, insert from table

double_args = (...) ->
   [x * 2 for x in *{...}]

tuples = [{k, v} for k,v in ipairs my_table]

當有錯誤發生時,它還能指出是在原先檔案裡的哪一行出錯,而不僅是編譯後的檔案。


安裝

最容易的方式是利用LuaRocks,以底下提供的rockspec來安裝:
> luarocks build http://moonscript.org/rocks/moonscript-0.1.0-1.rockspec

詳細安裝過程,我寫在另外一篇。

選用功能

如果你使用Linux,並且想用watch模式,此模式會監視.moon檔,當有變動時就自動編譯成.lua檔。你需要安裝linotify。


原始碼

專案原始碼放在GitHub上:https://github.com/leafo/moonscript

有任何問題,請到這裡回報:https://github.com/leafo/moonscript/issues

最新的開發版本(或許不能動喔)可以用底下的rockspec安裝:
> luarocks build http://moonscript.org/rocks/moonscript-dev-1.rockspec


相依於其他軟體套件

除了Lua 5.1外,MoonScript還需要底下的Lua模組:
  • linotify(在Linux上的選用功能)

若你使用LuaRocks來安裝MoonScript,這些套件都會被自動取回並安裝。


學習

完整的語言參考手冊(英文)在此。


其他外掛

差異處的概略介紹
  • 利用縮排與空白字元來定義出程式區塊
  • 所有變數宣告,預設為區域變數。
  • 用export關鍵字來宣告全域變數,用import關鍵字來匯入table裡的東西,也就是取得一份區域性的拷貝。
  • 函式呼叫時,括號是可有可無的,類似於Ruby。
  • 胖箭頭,=>,用來產生具有self參數的函式。
  • 在名稱之前加上@(小老鼠),用來指稱它是個self裡的東西。
  • 運算子!(驚嘆號),可用來呼叫無參數的函式。
  • 根據函式裡最後一個述句的型別,自動加上implicit return。
  • 使用:(冒號)來分開table裡的鍵與值,而不是用=(等號)。
  • 換行(newline)可用來區分開table裡的每一項目,逗號(,)也可以。
  • 用\(反斜線)來呼叫物件的方法,而不是用:(冒號)。
  • 支援+=、-=、/=、*=、%=運算子。
  • !=是~=的別名。
  • table comprehension,很便利的slicing與iterator語法。
  • 程式碼若一行,可以在後面加上迴圈與if述句。
  • if述句,可當做運算式使用。
  • 具有繼承的類別系統,建構在metatable __index屬性之上。
  • 建構子的參數,若以@開頭,會自動指定給物件。
  • 魔法般的super函式,將同名的類別方法對應到父類別的方法。
  • with述句,讓你以較短的語法存取無名的物件。


關於

MoonScript的語法,有很大程度是被CoffeeScript激發而來的。

沒有LPeg這個超棒超強的語法解析工具,MoonScript是不可能誕生的。

2011/08/21

MoonScript安裝

這一篇要講如何安裝MoonScript,至於概觀介紹可看這一篇。

我先試著在Windows XP與Cygwin上安裝,但都沒有成功,MoonScript作者也說他主要的開發平台是Linux,所以我才在我的小白Mac OS X 10.6.8上安裝,滿順利的,後來又在Windows XP裡的VirtualBox裝Ubuntu 11.04,也滿順利的。


概略步驟如下:
1. 安裝Lua
2. 安裝LuaRocks
3. 安裝MoonScript(其中某部分需要安裝git)


在Windows上的安裝過程:
我沒有成功,你可以參考這一篇,看看眾多複雜的過程,以及最後出現的問題。


在Ubuntu上的安裝過程:
因為軟體都以套件打包好了,所以安裝很順利。

1. 安裝Lua
sudo apt-get install lua5.1

2. 安裝LuaRocks
sudo apt-get install luarocks

3. 安裝MoonScript(其中某部分需要安裝git)
sudo apt-get install git(若你沒有安裝過git的話)
然後以LuaRocks安裝MoonScript(底下這行指令是MoonScript官方網站寫的):
luarocks build http://moonscript.org/rocks/moonscript-0.1.0-1.rockspec

MoonScript需要LPeg、LuaFileSystem、alt-getopt這三個套件,所以會先安裝它們。

安裝成功後,moon跟moonc這兩個執行檔會在~/.luarocks/bin/下。


在Mac OS X上的安裝過程:
1. 安裝Lua
到官方網站的下載區下載原始碼,我下載安裝的版本是lua-5.1.4.tar.gz。
tar zxvf lua-5.1.4.tar.gz,解壓縮。
cd lua-5.1.4,進解壓縮後的目錄裡。
make macosx,建構。
make install (sudo make install),安裝。

預設值會安裝到/usr/local下,執行檔(lua與luac)在bin底下,其他檔案散佈在include、lib、man、share底下。

2. 安裝LuaRocks
到官方網站下載,我下載的是luarocks-2.0.5.tar.gz,

tar zxvf luarocks-2.0.5.tar.gz,解壓縮。
cd luarocks-2.0.5,進解壓縮後的目錄裡。
./configure
make
sudo make install

預設值會安裝到/usr/local下,執行檔(luarocks與luarocks-admin)在bin底下。


3. 安裝MoonScript(其中某部分需要安裝git)
到這裡抓取Mac OS X的git安裝程式,按照下載後的dmg檔裡面的README.txt的指示安裝。

然後以LuaRocks安裝MoonScript(底下這行指令是MoonScript官方網站寫的):
luarocks build http://moonscript.org/rocks/moonscript-0.1.0-1.rockspec

MoonScript相依於LPeg、LuaFileSystem、alt-getopt這三個套件,所以會先安裝它們。


安裝成功的話,最後應該會出現如下的訊息:
moonscript 0.1.0-1 is now built and installed in /usr/local/ (license: MIT)

moon a.moon to run moonscript script file
moonc a.moon to compile it to lua code

moon跟moonc這兩個執行檔會在/usr/local/bin/下。


接下來就是寫MoonScript的程式,附檔名用.moon,以"moon xyz.moon"來直接執行,或是以"moonc xyz.moon"把MoonScript程式轉成Lua程式。請確定moon與moonc這兩支執行檔有在你的執行路徑PATH下。

完整的語言參考手冊在這裡(英文)。

2011/05/22

一致性

在很久很久以前,看完一本書,通常是指某程式語言的書,就可動手開發軟體了,現在可不同了(早就不同了),就算你能在24小時內學會某語言,你還要學會成堆成山的函式庫與類別庫,許許多多約定俗成的慣用法與程式片段,多少的青春歲月就花在這些層層疊疊的架構中,多少的辛酸血淚就灑在這些有臭蟲又不更新、不想用又不得不用的醜陋框架(framework)裡,唉,怎一聲長嘆了得。

寫程式時,需要一種天份,能夠在短時間內記住很多小細節的天份,變數名稱、函式名稱、回傳值、上下繼承關係、這段程式碼應該要寫在哪裡才好、等等,久了之後,自然而然熟極而流,越寫越順,當你能夠控制環境,這裡指的是掌握開發軟體時的環境,就會感到快樂,對每一行都確實知道在幹嘛,不用寫一寫就要停下來查文件,能夠處於那種優遊自在的狀態,真是美妙地不可言喻啊,所以,如果很不幸地要你換一種語言、換一套開發環境,那將會是極端苦痛、痛不可言、言語無法形容地不幸啊,光程式語言本身就會有很多的差異,有大有小,大的也就算了,畢竟是不同的語言,如果沒有一定程度以上的差別,那幹嘛存在多一種的語言了,至於小的,就很惱人了,有些一樣、有些差不多一樣、有些不太一樣、有些不一樣又有相似處,煩死啦。

請看一下底下這兩張圖,這一張是Perl的布林比較,當我在Perl書裡讀到其比較的判斷法則時,心理只有一種想法,瘋啦瘋啦,誰能記住這玩意兒啊?這什麼規則啊?(當然,我記不住不代表別人記不住。)


這一張是JavaScript的,好像好一點,但也處處是陷阱,一不小心程式的結果就會跟你想的不一樣。


顯然地,各程式語言的創作者對於這麼一個小小地方都能夠有眾多分歧的意見,什麼是true、什麼是false,nil又能代表什麼,nil能代表空串列嗎、nil等同於false嗎,等等等等。


當某語言有太多細微細節要花心思記得的話,用起來就很礙手礙腳,我認為Perl就是其中之一,Perl還標榜著"There's more than one way to do it",聽起來是不錯,不過怎麼能記住這種種不同的用法呢?通常是選出一兩種好的,然後一直用吧。C++比較好一點但也差不多,當開始使用C++各種較進階的功能時,例如exception或template或operator overloading,哇賽,各種眉眉角角就出現了,要記住的地方就變多了。


很佩服那些跨平台的框架或軟體,光要搞定windows、mac、linux就快死人了,其中的差異可不是隨便就能講完列出來的,若是說到手持式裝置上,那就更加分歧了,再加上封閉、保護公司資產、追求與眾不同市場區隔、刻意與別人不一樣,嘿,真是混亂的世界啊。


在這裡有個小小的希望,至少、至少在語言層級,能夠有一致性,不是每個人都能像翻譯機精通八國語言聽說讀寫樣樣行,我知道,語言設計者是大師們,他們有思想有文化有所堅持,但是還是請你們至少把布林比較這麼基本的地方設計出一致性吧。


如果你對程式語言的歷史以及各種語言特性有興趣的話,看看這段影片吧。這根本是大師級的火力展示,裡面一堆聽都沒聽過的語言,例如KRL、COMIT、等等。

2011/03/30

翻譯:多型不行時(When Polymorphism Fails )by Steve Yegge

文章:When Polymorphism Fails(多型不行時)
日期:2004.08.25
作者:Steve Yegge
作者的部落格(2006至今):Stevey's Blog Rants
作者舊的文章(2004與2005):Stevey's Drunken Blog Rants

作者簡介:
目前任職於Google,之前曾任職於Geoworks與Amazon。程式語言生涯中有過兩次非常關鍵性的轉換,一次是組合語言,一次是Java 跟Perl,因為發現解決語言本身設計帶來的問題所花的時間,竟然比真正用在開發軟體系統的時間還多。其文章以長度聞名,長到應該稱為論文而非部落格,兼帶詼諧筆風,發表頻率大約一個月一到兩篇,作者總是說這些是在凌晨三罐啤酒下肚後的誇誇其談,但每一篇都是經過長時間醞釀,內容充實有見地的傑作。

獨立以Java/JPyton開發多人線上遊戲Wyvern,可讓玩家自行創建擴充遊戲內容。其工作團隊將Rails移植到Rhino上,Rhino是運作於JVM平台上的JavaScript引擎,Rails(Ruby on Rails)為一套受到廣泛喜愛使用的網站開發模組;為Emacs撰寫完整的JavaScript環境,期望在Emacs上可以有一套 JavaScript IDE,以及將來可用JavaScript而非elisp來開發Emacs extensions。


多型不行時(When Polymorphism Fails )


每個愛好OOP(object-oriented programming)的人順理成章地也會是多型(polymorphism)的狂熱份子,有很多本來可以更好的書籍(譬如Fowler那本講重構(refactoring)的),竭盡所能地傳達出一種信念:如果你用了執行期間型別檢查(runtime type checking),也就是Java的"instanceof"運算子,那麼你大概是個壞胚子,是那種會手拿switch恐嚇小孩子的邪惡角色。

總括來說,若是用上了"instanceof",這通常就表示程式的OO作的不好,這點我是同意的,那表示設計的技巧不足,兩相比較起來,應該要盡量使用多型,而不是執行期間型別檢查,寫出來的程式碼會比較乾淨比較容易維護,然而,我認為,至少存在一種狀況,這種狀況很常見到處都有,有資格稱為一個範式,在這種情況下你根本無法使用多型,如果你知道怎麼用,拜託告訴我,我很渴望知道,但我認為不太可能,至少在Java或C++這種靜態語言裡是不可能的。


多型的定義

若是你不是很熟悉OOP的術語,讓我解釋一下,多型是個矯揉造作的詞彙,它的概念其實就是延遲連結(late binding),而延遲連結這個詞彙也很矯揉造作(繼續往下看你會知道其中的奧妙),它的意思是,把找出哪個方法(method)要被呼叫的決定延遲到執行期間才作,到那時才判斷被呼叫的對象目標是不是能回應訊息。

以效能為優先考量的語言,譬如C++、Java、OCaml,方法(methods)會被賦予數字編號,每個類別都有一張表記錄著有哪些方法,在執行時會掃描這張表找出被呼叫的方法;而另外一方傾向於在執行期間時能夠擁有較大彈性的語言陣營,其找出方法的手段,通常是對方法的名字作雜湊(hashing)來判斷,除此之外,這兩種方式其實可看做是同等的。

光有虛擬方法(virtual methods)並不會帶來多型,多型冒出來的條件是:當你有好幾個子類別(subclass)繼承自某一個類別,每一個子類別都實作自己的多型方法,而且各自處理方式皆不相同。舉一個教科書上用到爛的例子,如果你去動物園,你會看到,動物們處理createBadSmell()訊息的方法都不一樣,嗯,或許從某些觀點來看會有共通極為類似之處,我猜啊,做那回事時的聲音大小算是個重點,我還是無法判斷出,到底是河馬還是長頸鹿的表現能力比較好,不過,有興趣的話你可以在過段時間後再來問問我。


多型大搖大擺地炫耀著

舉個實際一點的多型範例吧,讓我們來看看一個面試中會問的經典評估問題,就我所知,這是由Ron Braunstein帶進Amazon的,這問題的內涵相當豐富,企圖要探測出求職受試者是否擁有各種重要的技能:OOP設計、遞迴、二元樹、多型與執行期間型別檢查、一般撰碼技巧、以及(如果你要搞成這麼難的話)語法解析理論(parsing theory)。

進行在某個時間點,求職者應該能夠領悟到,可以把數學運算式以二元樹表示出來,在此假設只有二元運算子的情況,如"+"、"-"、"*"、"/",樹葉節點皆為數字,內部節點皆為運算子,想求出運算式的值就表示要把這棵樹走過一遍,如果受試者想不到這點,你可以適當地引導他們走到這一步,或是,若有必要的話,直接告訴他們也行。

即使你直接提示他們,這仍然會是個有趣的問題。

這問題的前半部分,某些人(為了人身安全我不敢點名,但他們的名字字首是Willie Lewis)覺得"如果你叫自己是個程式開發人員而且想在Amazon工作的話,這是一定要會的啦",但事實上是有些困難的,問題的前半部分是:你如何把一個數學運算式(例如在一個字串裡),例如是"2 + (2)",轉換成一棵運算樹,在我們的ADJ挑戰賽中應該有出現過這一題。

問題的後半部分是:假設這是個兩人專案,你的夥伴,讓我們叫他"Willie",負責把字串轉成樹,你負責簡單的部份:決定Willie應該用什麼類別把樹建構出來。你可以挑任何一種語言來用,但一定要確實挑出一種來,要不然Willie會用組合語言寫好交給你,要是不幸遇到他心情不好,那麼會更慘,他會用那種處理器已經停產的組合語言寫給你。

有很多面試求職者被這梗擊中爆笑不已喔,驚訝吧。

我不會給出解答,不過,標準的壞答案是,使用switch或case的語法(或是老派一點,一連串的if);稍微好一點點的答案是,使用儲存函式指標的表格;而大概稱得上最佳的解答是,使用多型。我鼓勵你做做看這題,很有趣喔!

諷刺的是(底下你就會看到),如果你想要打造一套可擴充有延伸性的系統,使用多型是相當理想的解答,如果你想要在系統內加入新的函式,而不想重新編譯整個系統──特別是,不想在那個有500個case的巨大分支指令裡加入更多的case,那麼,你就會想要使用多型。


用三種多型形式的歡呼來為多型喝采

所以,多型,講了這麼多,看起來還滿有用的。至今,多型最有用的地方是,呃,多型print。如果你用過Java、Python、Ruby或其他"真正"的OO語言,那麼多型print對你來說不是什麼新鮮事,你下命令叫物件把自己印出來,天呀,就印出來了,每個物件會印出內部狀態,恰恰好是你所需要知道的,在除錯、追蹤、記錄時非常有用,甚至在製作說明文件時也很有用。

如果你用的是有著扭曲OO外表的語言,例如C++或Perl這種強加裝上物件導向配件的語言,就好像是把一對價值美金2500的輪胎邊框裝在1978年的速霸陸老舊車款上,那麼,你能用的就是除錯器,或是Data::Dumper,或是類似的東西。哈,可憐喔,你啊!

(反問句:為什麼我們選擇使用C++與Perl呢?此兩者為世上最爛的語言是也!照同一標準的話,也可以用Pascal和Cobol啊,哭吧,大聲一點。)

順便說一下,最近我都沒提到OCaml,主要原因就是多型print,OCaml沒有提供多型print,原因我還沒完全搞懂,但大概落在"設計者的嚴重精神失常"那附近,因此,你沒辦法為了除錯而隨便叫一個物件在主控台上print出來;我希望他們這麼設計是有其必須性的,譬如為了能讓OCaml達到那擊敗C++效能表現的神奇傳說,若非如此,那就好像在使用便利性上打了一巴掌污辱人嘛,不過至少OCaml有支能夠穿越時空往回走的除錯器,你必定會需要它的。

總之!我們喜歡多型,多型就是微控管理的反面,你叫物件做事情,無需告訴他們要怎麼做,而他們就會忠實無誤地到網路看上整天的Strong Bad影片,嘿,那群笨笨的物件啊!愛死他們了。

可是,可是,多型啊,就好像所有的正義角色,擁有黑暗的一面,就多型這個案例來說,並不像,嗯,安納金天行者的黑暗面那麼黑,不過,不管怎麼說,黑暗面就是黑暗面。


多型的矛盾

要在程式碼裡使用多型,有個不明顯但非常要緊的條件:在之後你還能增加東西進去。至少,對靜態語言來說,例如Java與C++,如果你增加了一個多型方法,你必須重新編譯那些實作出方法的類別,也就是說,你需要擁有原始程式碼,並且能夠進行修改。

在我的能力範圍內,至少可以說出,的確有某一類系統是做不到這件事的,那就是:可擴充系統(extensible systems)。

讓我們假定,你正在打造一套嶄新的系統,允許使用者自行加進程式碼,這可不是件簡單的任務,有很多理由要考量,包括保密安全性、緒程安全、以及其他許許多多的地方,不過這種系統是存在的!譬如說,有些線上遊戲可以讓玩家放進他們自己寫的程式碼,毋須存取原先的遊戲原始碼程式,大部分的多人線上遊戲都朝著這個方向前進──管理階層意識到,使用者想要而且有能力創造出好的內容出來,所以讓遊戲把APIs公開出來,讓玩家能夠打造他們自己的怪物、法術等等,藉此擴充延伸遊戲系統。

嗯,我聽到某種聲音告訴我說,網站服務(web services)也走向類似的道路。

在任何時候你想打造出這種可讓使用者進行擴充的系統,就需要花費三倍多的心力,多出來的兩倍工作量在於,把你內部APIs與類別安排整理好,使得它們是可以被使用者所修改運用的。

Java的Swing就是個很好的範例,打造可擴充系統時,你就會跑進創造者矛盾(Inventor's Paradox)的弔詭中,你可以上網找找看,不過它的中心大意就是,你沒辦法事先預想出,你的使用者想要對系統的哪些部分進行修改。你可以把限制一直往外推──就算是你把系統裡的每一行程式碼都搞成虛擬函式並公開出去──即便如此,你的使用者最終仍然會跑到某個他們沒辦法客製修改的地方。這真不幸啊,我也不會假裝我知道答案。Swing的應對方式是:搞出一拖拉庫的掛勾(hooks),使得它變成一套龐大無比的API而很難搞懂。


怪物問題

為了讓說明更加具體,讓我們回到線上遊戲的案例,假設,你已經極盡所能地把APIs與類別都弄好公開出去,可以用來創造與操縱法術、怪物、以及其他遊戲中的物件,假設你已經寫好許許多多的怪物類別了,我想你一定可以想出不少。

現在,假想有一個玩家,想要參與並寫一隻叫做OpinionatedElf小怪物。這是個故意設計出來的假設,就好像自動機停機問題(halting problem)證明自己的論點那樣不自然,不過,在實際上的確會發生類似的狀況。

假設,這隻OpinionatedElf其唯一的使命,就是大聲嚷嚷著他喜歡還是不喜歡其他種類的怪物,他會坐在你肩膀上,無論何時當你碰見一隻,譬如說獸人(orc),他就會歇斯底里抓狂叫喊著:"我恨獸人!!!啊啊啊啊啊!!!"(附帶一提,這句話就是我對C++的感覺。)

這個問題的多型解法相當簡單:找出你那150個怪物類別,每一個都加進doesMrOpinionatedElfHateYou()方法。

天啊,聽起來白痴極了,但是,這真的就是使用多型的解法,沒錯吧?如果你有一堆類似的物件(此例就是一堆怪物),這些物件都要根據某種狀況做出不同的回應,所以,你加進一個虛擬方法,然後替每個物件實作出不同的程式碼,沒錯吧?

顯而易見,這該死的解法行不通,即使你硬來(你沒辦法硬來,因為撰寫這隻小elf的玩家並不持有原始碼),也是一種差勁的設計法,沒道理把這麼特殊的方法(method)放進遊戲裡每個怪物物件裡,這點很清楚,假如一段時間後,我們發現OpinionatedElf有版權問題而必須移除掉,怎麼辦?到時你就必須回頭找出那150個怪物類別,一個一個移除掉。

就我所知(我並沒有聲稱自己是個程式設計高手,僅是個想知道正確答案的人罷了),正確解法會是使用執行期間型別檢查(runtime typing),程式碼大概會長的像下面這樣:

    public boolean doesElfLikeIt ( Monster mon )
    {
        if ( mon instanceof Orc ) { return false; }
        if ( mon instanceof Elf ) { return true; }
        ...
    }


我知道我知道,你這個OO狂,你可以寫出150個附加類別來幫助OpinionatedElf,每一種怪物由一個類別負責,可是,那樣並沒有真正解決根本的問題喔,問題核心所在是,這些眾多紛紜的行為全部都落在呼叫者(caller)身上,而不是被呼叫者(callee),那就是這些行為的歸屬之地,在呼叫者裡,就是那裡。

較高階的程式語言,對這個問題擁有稍微好一點的優雅解法,注意,我強調只是"稍微好一點",譬如說Ruby,你可以為其他類別加進新的方法(methods),即使是內建的類別也行,即使是你沒有原始碼也行,舉例而言,你可以把下面程式碼放進OpinionatedElf檔案裡:

    class Orc
      def doesElfLikeMe; return false; end
    end

    class Troll
      def doesElfLikeMe; return false; end
    end

    class ElfMaiden
      def doesElfLikeMe; return true; end
    end

    ...

如果還沒載入的話,Ruby實際上真的會把你指定的類別通通都載入,並且把你加入的方法放進每個類別裡,一般而言,這是個相當不錯的特色。

不過呢,在這裡讓我來說說這種作法的優缺。對Ruby(以及其他大部分高階語言)來說,方法(methods)就是放在雜湊表(hashtable)裡的項目罷了,每個類別分別有一張表,上面所述的特色,實際上發生的動作就是,跑進每個怪物類別裡,把你的東西塞進雜湊表裡去。優點有:

    * 所有關於OpinionatedElf的程式碼都被封裝在他的檔案裡面
    * 在elf檔案被載入前這些程式碼並不會被載入
    * 系統裡的任何其他人都可以向某物件詢問,問elf喜不喜歡他

缺點是當加入了新怪物,而elf無法辨別時,此時你需要有預設的行為。如果某人加入怪物類別小精靈(gremlin),你的elf就會卡住,亂吼亂叫地說"啊呀,那是什麼啊?",直到你更新加入處理小精靈的程式碼為止。

我猜想,如果你能以某種手法列舉出系統裡的所有類別,然後檢查是不是繼承自怪物類別,然後就應該能以數行程式碼達到上面所說的。若是Ruby,我打賭你做得到...不過僅限於已經被載入的類別,而對那些還躺在磁碟裡的類別可沒用啊!你或許還是可以想辦法解決,但可別忘了網路的存在啊...

話雖如此,需要有預設的行為這點,還不算太糟,還有更慘的缺點,緒程安全(thread-safety)是其中之一,這點深深地困擾著我──以這個案例來說,我不認為Ruby對於緒程安全所下的語意是定義清楚的,有把類別鎖住(class lock)嗎?載入elf類別前所執行的物件,其執行所在緒程,鎖定又會做些什麼呢?我日文不夠好,無法得知證明的方法存在於標準規格文件中還是在實作程式碼裡。

但是,真正的問題,真正困擾我的問題是,你的程式碼會跑進系統裡並且散佈到所有的類別中,這感覺起來,就像是違反了封裝(encapsulation)原則。

事實上,比那還要更深層一點,這感覺起來像是個差勁的設計,我們的情況是,有個觀察者,做出判斷的呼叫,而我們把判斷的程式碼塞進被觀察者裡,這不就好像是,我走到所有員工面前,交給他們一塊牌子,說:"請拿著這張牌子,上面寫著我喜歡或是不喜歡你"。這可不是真實世界的運作方式,而OOP不是應該要模擬這個世界嗎。


再次審視多型

嘿!至此,我已經說的相當明白了,多型不再能被當作銀色子彈了,即使是一個無擴充性的系統,如果你需要根據目標對象的型別來決定做什麼動作,那麼,把判斷的程式碼放進目標類別裡是不對不好的。

一個更實際更樸實的例子是,身分認證(authentication),讓我問你:如果你要設計一套權限管理系統,你的設計裡會有一個虛擬的doYouHaveAccess()方法,然後讓所有來人自行實作該方法?換句話說,你會請一個保全警衛,讓他去問每個人是否有權限進入建築物嗎?

絕對不會,你會在程式碼裡做執行期間型別檢查:

    public boolean denyEntryToBuilding ( Person p )
    {
    return ( p.hasNoBadge() ||
         p.isSuspiciousLooking() ||
         p.hasMachineGun() );
    }

但是,等一下,這些檢查並不是公開檢查類別啊,譬如,我並沒有說"p instance of MachineGunBearer",這是怎麼回事?

嗯,所謂物件的"型別(type)",實際而言,指的是它的類別(class)(幾乎是寫死的,就像是基因)以及屬性(properties)總合起來的合集,在執行期間可能會也可能不會改變。

那將是另一篇部落格了,但,我認為這代表了,型別最好由屬性而非類別來代表,因為類別有著與生俱來的固定性,但是在"傳統"語言裡,像是C++與Java,這會讓程式碼更難以共享,因為沒有支援語法委託(syntactic delegation)的特色,如果你覺得我說的沒道理,it's OK:我正在喝第三杯酒,快要不省人事了,讓我們把這個題目留到另一篇文章罷。

在此,我希望我把論點說的夠清楚了,也就是,當多重行為是目標對象的行為時,使用多型才有意義;當行為屬於觀察者一方的話,你需要執行期間型別檢查。


總結

嗯,我希望你在今天的醉後部落格有學到一些東西,我知道我有,其一,我學到Google的搜尋引擎的確有足夠的智能可以把"anikin skywalker"修正為"你是說anakin skywalker嗎?"哎呀,那群厲害的渾球,這不像是他們擁有著作權呀。

我也學到了,一篇部落格的長度應該剛好是兩杯葡萄酒,如果超過,你就會開始半昏迷語無倫次,打字也會打到地獄去了。

下週見,接下來的主題是...

Stevey's Drunken Blog Rants(tm)
(發表於2004年8月25日)

2011/03/28

遞迴很重要啊

Recursion(遞迴、遞歸、迴歸),很重要啊。

最近,幫某人檢查他寫的程式,是用Matlab寫的,雖然我不太會,但他也沒用到什麼了不起的特色功能或是函式庫,所以我邊看邊學倒也OK。

他需要看一篇論文,理論證明的部份搞懂後,論文最後有跑程式模擬得出數據,但是論文作者似乎沒有公布原始碼,所以指導教授要他寫出程式出來,一來可以實際動手並且更了解論文內容,二來,有新的理論模型時,可以修改程式,跑出新的數據,測試看看新的模型可不可行,可行的話,我想那就是一篇論文囉。

細節部分就不多說,最難(對他來說)的一個地方是,給定n個東西,要用窮舉法排列出所有的排列組合(總共n!種),每種排列組合都要做某種運算,所有組合的運算值得知後,找出其中最大的值。

老實說,當我看到他用iteration(疊代)的方式來寫,也就是說用loop(迴圈)來寫,真的嚇了我一跳,因為,我看不懂啊!我雖然幫他用recursion的方式寫了那部分的程式碼,不過他執意要用迴圈寫,唉,搞不懂為什麼耶。經過一番努力後,我終於看懂網路上用iteration寫recursion的程式碼,不過我還是沒看懂他的程式碼,Orz。


遞迴很重要啊,很多演算法用遞迴寫是多麼自然、多麼簡單啊,會寫程式的人可別說你不會recursion。

翻譯:趕羚羊之程式設計宣言(The Motherfucking Manifesto For Programming, Motherfuckers)by Zed A. Shaw

文章:The Motherfucking Manifesto For Programming, Motherfuckers 趕羚羊之程式設計宣言
日期:2011.03
作者:Zed A. Shaw
作者的部落格:ZED SHAW


趕羚羊之程式設計宣言 (The Motherfucking Manifesto For Programming, Motherfuckers) 




草泥馬們看過來,
這是一份程式設計之
趕羚羊宣言。


我們是一群每天都在趕羚羊的程式設計獅,多少年來,我們被所謂的軟體開發方法論(software development methodologies)不斷地羞辱踐踏。

我們已經受夠了,不論是插屁(XP)、爭球(Scrum)、看版(Kanban)、瀑布(Waterfall)、軟體工匠技藝(software craftsmanship,也稱為戳屁屁"XP-Lite"),還是任何其他阻礙干擾程式設計的一切事物,夠了夠了,草泥馬的咧。

我們真的受夠了,我們被認定是孤僻且以自我為中心的蠢蛋白痴,說我們必須被加以操控,必須被束縛在強迫配對(Forced Pair Programming)的模式下工作,而沒有一滴滴的時間可以發揮創意創造力,因為,專案裡的那10位經理,沒有一個能夠做...程式設計,賽羚羊咧。

我們必須摧毀這些妨礙程式設計工作的方法學,草枝擺啦。

我們的價值

他們宣稱會衡量的價值他們實際上衡量的價值 我們真正要幹的
個體能力以及與他人的交流分工許許多多可換算成薪資的工作時數程式設計,趕羚羊啦
能跑能用的軟體可用的單元測試(unit tests)程式設計,趕羚羊啦
與客戶間的協力合作把客戶榨到乾程式設計,趕羚羊啦
對需求變更的回應能力不穩定性與貌似老實卻有推諉不知情的能力程式設計,趕羚羊啦

我們認為,左邊那排米田共,其實就是把中間那排反過來看,而我們真正需要做的是在右邊那排...程式設計,法克魷。

簽名:
Zed A. Shaw與會趕羚羊的程式赦祭屍。

變成一位程式色妓蝨獅然後趕羚羊吧

如果你不會但想學寫程式,那麼,我大力推薦我寫的書Learn Python The Hard Way,寫的真是太好了,不論你的程度高低,任何人都可以讀一讀,至少可以學會了解趕羚羊...ㄟ...程式設計的基本功夫,贛林老母啦。

寫程式取名稱令人抓狂啊

在Reddit Programming上看到一個連結,OpenBSD shutdown.c, line 93,一看,笑死我了啦,哈哈哈。

1.34      cloder     93: void __dead die_you_gravy_sucking_pig_dog(void);

哇哩咧,這是什麼東東啊,左思右想就是不了解這個函式到底有啥用途?惡作劇?亂搞?開發人員發瘋了?精神異常?還是想不出好名稱了?

命名真的是一件很難的事情啊,取的不好的話心裡就會有個疙瘩在,取的好就神清氣爽,譬如有人把null terminated pascal strings以fucked strings稱呼之,真是妙啊,哈哈。有個叫做匈牙利命名法(Hungarian naming convention) 的規則,可以參考參考,不過根據Joel的說法,大家都誤解誤用這套規則了。

回到上面那個函式的命名,我想,應該跟命名學沒什麼關係,而是壓力太大的惡搞結果吧。

2010/12/30

git and Beyond Compare 3 on Windows XP + Cygwin

這篇記錄一下我git的設定,使用Beyond Compare 3來做為git的diff與merge時的工具。我的電腦是Windows XP,我用的是Cygwin裡的git,不是msysgit。

修改每個repository裡的.git/config或是家目錄裡面的~/.gitconfig。

[diff]
    tool = bc3
[difftool]
    prompt = false
[difftool "bc3"]
    cmd = \"c:/program files/beyond compare 3/bcomp.exe\" "$(cygpath -w $LOCAL)" "$REMOTE"
[merge]
    tool = bc3
[mergetool]
    prompt = false
[mergetool "bc3"]
    #trustExitCode = true
    cmd = \"c:/program files/beyond compare 3/bcomp.exe\" "$LOCAL" "$REMOTE" "$BASE" "$MERGED"

裡面用了cygpath把cygwin下的路徑$LOCAL(類似/tmp/U5VvP1_abc這種東西)轉成windows的路徑,因為bc3是個windows下的軟體。

然後,可以用git difftool來比較檔案,git mergetool來做整合。

關於truExitCode的意思是,若外部工具的exit code可以代表檔案整合的成功與否,那麼就可以設為true,要不然,git mergetool會向使用者詢問整合是否成功。詳情請見說明文件。

2010/12/11

翻譯:這不是物件導向程式設計啦(What OOP Isn't )by Benjamin Supnik

文章:What OOP Isn't(這不是物件導向程式設計啦)
日期:2010.12.09
作者:Benjamin Supnik
作者的部落格:The Hacks of Life


這不是物件導向程式設計啦(What OOP Isn't)


想當初,我開始上第一堂的電腦科學課程(不過在那以前,我已經自己開始寫程式有一段時間了),系上教職員正在打一場內戰,某部分人信仰著物件導向程式設計(object-oriented programming, OOP),他們相信世間萬物皆物件,物件就是王道啊,他們教授要我們學生用OOP的方式來實作鏈結串列(linked list):哇賽,每一個節點都是被完完全全封裝起來的物件!


(什麼,你有興趣知道嗎,讓我告訴你,串列大部分的編輯動作,都必須是堆疊-遞迴式(stack-recursive)──當呼叫一個節點的next時,節點要把它的'next'設定為呼叫的回傳值,讓某節點能夠'把自己切掉'。嘿嘿,聽的懂算你強,這讓那些從沒碰過鏈結串列的學生,一個頭兩個大,根本不知道到底自己在幹嘛,因為,他們在同一個時間,碰到了遞迴與鏈結串列,而寫出來的程式,結果是,執行起來有著LISP的效能,而程式碼看起來像C++的優雅,真是糟糕可怕啊。)


他們說,OOP的三大支柱是封裝、多型、繼承(encapsulation, polymorphism, inheritance),我以前已經發表文章說過為什麼最後一項通常會是個不好的概念,回想那時在學校的時光,我還沒有足夠的程式開發經驗,所以只能說,當時我們被教的東西(全部都是OOP,不論何時何地)比我一直以來在寫的程式(用物件來表示某種很大的東西,例如遊戲裡的一個角色)要難上好幾倍,寫出來的程式碼,又囉嗦也沒有特別快,現在,我有了軟體工程的經驗,我想,可以把其中問題所在更精確地描述出來。


有時,看到程式新手眼裡閃著OOP字眼,努力學會這到底在講些什麼,我會跟他們說,封裝、多型、繼承的相對重要程度大概是90%、10%、0%,OOP絕大部分的價值所在,是它提供機制與作法,可以把一段程式碼跟其他不相干的程式碼分離開來,而不會混雜在一起,而這點對大型軟體開發專案很重要,而你不可能教會大學生這一點,因為他們還沒有夠多的程式開發經驗,無法體會其中的重要性。


多型也不錯,但據我的經驗,它不像封裝那麼有用,如果你有個多型介面,有個介面就表示,它是被封裝的,但是,有許多的案例顯示,有些介面就只會用一次,而且沒有多型這項性質,粗略來講大概是90%-10%吧,所以我認為扮演舉足輕重角色的是封裝。當然,有些產品與領域比其他地方更常使用多型,WorldEditor(Laminar Research的開放原始碼場景編輯器)大部分的核心元件採用多型介面的架構,但X-Plane本身就很少。

我提出這點,因為我大概(在以後的發表文章中)會把OOP與其他技術(與軟體工程碰到的實質困難有關的)作個比較,不過OOP有一點歷史包袱,過去常常講,OOP可以讓我們變成更好的程式設計師,可以幫助我們寫出無臭蟲的程式碼,執行速度更快了,或是可以幫助差勁的程式設計人員升級成優良的,這些都被時間證明了,只不過是些天真白痴一廂情願的想法(特別是,差的程式設計師,不論給予什麼樣的技術或方法,照樣可以生產出低劣的程式碼,一大沱)。

所以,我希望幫OOP這玩意作類似如下的定義:在語言上提供好的支援來進行封裝,有時候需要有多型介面,加速撰碼。這對我來說就非常好用了!我可以僅用純C語言寫出相同的東西,但不僅需要花更多時間打字,還會讓我變成很嘮叨的人,不斷囉囉唆唆血壓上升,何苦呢?

2010/10/17

翻譯:皮諾丘難題(The Pinocchio Problem)by Steve Yegge

文章:The Pinocchio Problem 皮諾丘難題
日期:2007.01.17
作者:Steve Yegge
作者的部落格(2006至今):Stevey's Blog Rants
作者舊的文章(2004與2005):Stevey's Drunken Blog Rants

作者簡介:
目前任職於Google,之前任職於Geoworks與Amazon。程式語言生涯中有過兩次非常關鍵性的轉換,一次是組合語言,一次是Java 跟Perl,因為發現解決語言本身設計帶來的問題所花的時間,竟然比真正用在開發軟體系統的時間還多。其文章以長度聞名,長到應該稱為論文而非部落格,兼帶詼諧筆風,發表頻率大約一個月一到兩篇,作者總是說這些是在凌晨三罐啤酒下肚後的誇誇其談,但每一篇都是經過長時間醞釀,內容充實有見地的傑作。
獨立以Java/JPyton開發多人線上遊戲Wyvern,可讓玩家自行創建擴充遊戲內容。其工作團隊將Rails移植到Rhino上,Rhino是運作於JVM平台上的JavaScript引擎,Rails(Ruby on Rails)為一套受到廣泛喜愛使用的網站開發模組;為Emacs撰寫完整的JavaScript環境,期望在Emacs上可以有一套 JavaScript IDE,以及將來可用JavaScript而非elisp來開發Emacs extensions。


皮諾丘難題(The Pinocchio Problem)


過去一年來,我將自己每月花在寫部落格的時間限定在四小時左右,通常我會有很多主題可以寫、很多東西可以討論,選一個出來並不會太難,但上個月在我腦袋裡只有一件事在打轉,我不斷在嘗試著用各種方式把它說出來,都快抓狂了──你知道的,用一種又好又簡潔的方式表達出來,讓讀者能夠把它嚥下去,可惜到目前為止都沒成功,我想那代表我還不真的懂這主題,或許試著寫下來可以幫我更了解。

這主題是關於軟體設計;你瞧,似乎有著某種好方法可以用來設計軟體,甚至有人認為存在著一種最好的方法,但沒人實際運用啊,嗯,或許一小撮人有,即使如此,我認為那些少數例子有一半僅是碰巧吧了。

斷斷續續地,這個題目我已經思考研究了很長一段時間,十八年的光陰過去了,而我依然不能清楚明確地說出……嗯,設計的原則,如果這是我們要的答案的話;但上個月,我覺得我好像又更靠近了一點,或許你可以幫我一下!我會告訴你我知道的,那你可以告訴我你知道的,或許我們可以合作出某種非凡的結果。

先為今天這篇設定一些背景,讓我用一個句子將所有我寫過的文章內容做總結:我認為大部分的軟體都是大便。嗯,好像不夠正確,更誠實地說,我認為所有軟體都是大便;耶,沒錯,你聽到了,Stevey一言以蔽之:軟體皆大便。

即使如此,我認為某些軟體系統確實比其他的好:某些大便生產者可能有吃過一些金幣,所以他們的大便看起來比較亮晶晶一點,偶爾有人吞下紅寶石,他們的大便就又好看又值錢,嗯,記得站遠一點看。但是,不論吃進了多少寶貴的石頭,大出米田共就還是米田共。


多麼可愛有趣的譬喻啊,對吧?我自己想的喔!

每想到我最喜歡的軟體系統時──像熟悉的老朋友們、像適合居住的福地──我看到它們都擁有某些特質,很快我就會告訴你是什麼特質,至少是那些我注意到的,別急,我保證不拖戲,但事情還沒結束;一旦你將這些特質加進一個軟體系統中,如果做的對的話,那麼通常你可以得到一套跟時下做得出來的好軟體一樣好。不過,仍只能算是大便而已。

在我開始思考若能脫離屎的層次來打造軟體系統時,真正的難題就開始浮現,這樣的思索引發出各種有趣的問題,而我一點頭緒也沒有,但我會把這些問題也丟出來,只要我還沒超過四小時的時間限制,還沒被生活瑣事招喚過去的話。

我喜愛的系統

在上個月左右的某個時刻我領悟到一個重點,那就是,我喜歡的軟體系統所共同擁有的那些特質都是由一個根本原因所衍生出來的:某單一特質、或稱為設計原則,軟體如果擁有這特質的話會自然而然地呈現出正確的特性。

哪些是我喜愛的軟體系統呢?以下列出最好的幾個:Unix、Windows XP、Mac OS/X、Emacs、Microsoft Excel、Firefox、Ruby on Rails、Python、Ruby、Scheme、Common Lisp、LP Muds、Java Virtual Machine。

有幾個差一點點的:Microsoft Word、OmniGraffle Pro、JavaScript、Perforce。

還有幾個應該可以排上去,但我因不夠熟悉而不敢亂說的系統:GIMP、Mathematica、VIM、Lua、Internet Explorer。

流行的軟體系統大部分都挨不上名次,雖然它們都相當有用,但是我認為缺少了理想軟體設計的本質,例如:IntelliJ、Eclipse、Visual Studio、Java、C++、Perl、Nethack、Microsoft PowerPoint,所有任天堂與PlayStation的遊戲,幾乎所有的電腦遊戲,除了著名的毀滅戰士(Doom)與雷神之鎚(Quake),還有大部分的網站服務,包括那些很有用的Amazon.com或是Google Maps。

我就不賣關子了,我認為在所有的軟體設計理念之中,最最重要的原則是:系統毋須重新啟動。

如果你設計一套系統可以不需要重新啟動,那麼,你終將會,就算是以很迂迴的方式,終將實現出一套可永久存活的系統。

我上面列出的系統,仍需要偶爾重新啟動一下,也就是說,它們實際上的壽命會落在數十年到一百多年之間,其中有幾個才剛開始──大部分都在二十出頭跟三十出頭左右,少數幾個已經有四十年的資歷了,然而,它們還是離不朽不死的境界很遠很遠。

我心中第二最最重要的原則,實際上算是第一原則的推論,是一套系統必須能成長而毋須重新啟動;一套不能隨時間成長的系統是靜止的,其實不能算是一套系統,應該說是一支函式,它可能是支非常複雜的函式,可以接受很多不同的輸入與產生輸出,它可能是支非常有用的函式而且存活相當久的時間,但函式一定會被取代掉,或是被可成長的系統吸收掉;我現在相信,經過了將近二十年的思考研究,能夠成長改變而毋須重新啟動的系統可以永久地存活下去。

必備特質

下面列出目前世界上最優質軟體共有的幾點特性,我列出來的它們並非全都都有,我想只有少數幾套系統擁有我列出來的全部特點,你應該會發現,一套系統有的必要特質越多,就會越強大、更重要、活的更久。

注意:這些特點都是給程式設計師看的,芸芸使用者們在乎的功能跟一套系統的壽命無關;我相信適當的時刻一到,程式設計能力就會會變得跟識字能力一樣普遍,所以這不重要。

第一:偉大的軟體系統都有命令列模式(command shell),這是系統不可或缺的部份,與系統同生同存,系統設計者無法想像沒了命令列模式的日子要怎麼過啊,命令列模式是完整的系統操控介面:任何你以其他方式操作系統完成的動作都可以透過命令列模式達成,光搞一套優秀的命令列模式本身就是一個大主題(大部分的必備特質都是這樣的,想想吧),我概略說一下:一套優秀的命令列模式一定會包含下命令的語言(command language)、互動式的查詢工具、腳本語言(scripting language)、擴充機制、命令歷史紀錄機能、以及命令列編輯器(command-line editor),一套超棒的命令列模式就是一套好系統的最佳典範:可以比使用它的母系統存活更久並可以移棲到別套系統中。

所有現存的命令列模式都是大便,但它們是今日可以被打造出來的好軟體不可或缺的元件之一。

我們可以把Emacs想像成一套非常厲害的終極命令列模式:想想如果把命令列模式推到理論極限會是什麼樣的光景,或至少推到現今人們可以構思出來的境界。

最佳軟體系統共有的必備特質還有很多,命令列模式不是唯一一個,所以,讓我們繼續談下去。

偉大的系統還要包含“advice”,這項特質並沒有一個大家都同意的名稱,有時稱為hooks(掛勾)、或filters(篩檢器)、或aspect-oriented programming(剖面導向程式設計、面向側面的程式設計、觀點導向編程;譯註:這怎麼翻啊?);就我所知,Lisp最早有這項特質,並以advice稱之;所謂的advice是一套小小的開發框架,在系統執行某動作或函式時,讓你以置入程式碼的方式,在動作之前、之中、之後插入你想要的功能,藉以改變系統的行為;每套advice系統都不一樣,它能夠介入的範圍越廣──意思是,它可以修改系統行為的範圍越大──那麼使用它的母系統功能就越強大。

Ruby on Rails有一套小小的advice系統,稱為“filters”,它(只)可以附加行為到controller actions中,所謂controllers actions是處理描繪頁面要求的特殊函式,你沒辦法在系統中的其他函式加上“之前、之後、之中”的filter,不過透過Ruby的metaprogramming機制可以在某種程度上做到;但一套advice系統不能僅是紙上談兵理論上沒問題,那是不夠的,advice必須從頭從底層融合進系統之中,以first-class形式、以有詳細文件說明的介面開放出來;advice非常強大有用,即使是Rails中簡單的action-filtering系統,都釋放出驚人的彈性;想想沒有它的話,要怎麼寫支Rails程式啊。

Emacs有套非常精密的advice系統;Common Lisp有一套可以說是世上最強大的advice系統,這支語言這麼強有很大的因素就是因為它;Aspect-Oriented Programming企圖在Java語言放進一套advice系統,野心很大,但因為Java的先天限制,必須以附加在語言外的擴充機制出現,有自己的編譯器與其他工具,這麼一來就大大阻礙了大家採用的意願,另外一個障礙物是,Java程序員偏愛撰寫死寂的軟體系統,任何能讓系統有生命的呼吸跡象都讓他們感到頭痛不已。

我要承認我也感到頭動,有生命的軟體聽起來有點嚇人,不意外,大部分的程式設計師都偏好開發牽線木偶而非真實的活人,掌控牽線木偶容易多了,但我認為有生命的軟體更加有趣也更有用些,坦白說這是無論如何都不能避免的趨勢,所以我們最好努力了解,對我而言,那就代表著實際動手建造一個出來。

讓我們繼續往下說,世界級的軟體系統都有套擴充語言(extension language)與外掛系統(plug-in system)──讓開發者可以延伸系統的基礎機能,有時候外掛(plug-in)又稱為“mods”,這是一種機制,讓使用者們以設計者不須參與的方式來擴充延伸系統的能力。

Microsoft Excel有一套絕佳的mod系統,這是一套令人激賞的開發框架,幾乎就等於是一套平台了,如同其他優質的mod系統一樣,它是一層一層的,從簡單Excel macros(Excel巨集),一直到完整的COM介面,可以被VB甚至是Python或Ruby驅動,只要那語言有COM bindings的話。


讓毀滅戰士(Doom)與雷神之鎚(Quake)這麼受歡迎的必殺特質之一就是它們的mod系統,毀滅戰士有一些基本的機制,以腳本驅動你建造的關卡、敵人、甚至是遊戲運行邏輯;雷神之鎚有QuakeC,仍然是,我認為啦,電腦遊戲腳本驅動語言的優良標準,不過我的資訊有點過時了,因為過去五年多來我都在玩電視遊樂器,很悲哀地,那些遊戲都死的不能再死了。

最強的外掛系統,強到能夠把整支應用軟體以plug-in的形式開發出來,這就是Emacs跟Eclipse的核心理念;先有一層很小的啟動層,可以看做是整個系統的硬體,系統其餘的部分,其所涵蓋的範圍(通常被效能因素限制住),都是由擴充語言寫出來的。

Firefox有套外掛系統,這真的是一套大便,但有總比沒有好;如果你有一套外掛系統,你很快就會發現,一定會有一些發狂的開發人員學習使用它並且榨乾它的能力直到極限,這會讓你誤以為你建構出來的外掛系統還不錯,但它應該要有容易使用跟毋須重起系統這兩項特性;Firefox打破了這兩項重要的規則,所以它的壽命處於一種不穩定的狀態下:要不然修正這兩項,要不然更好的系統會出現把大家都吸引過去。

真的讓我吃驚的是,連Firefox extension的開發人員都沒有特殊的管道跳過重新啟動這條災厄之路,他們的開發流程很痛苦,每有更動就必須手動重新安裝外掛(使用GUI操作方式),藉由某些技巧可以避掉大約一半的步驟,但整個開發社群對這點真的在皺眉表示不滿了!他們很明顯地感受到,如果使用者要經驗安裝外掛的痛苦一次,那外掛開發人員每當改一行程式碼就必須痛一次,每一次都在提醒他們:Firefox的外掛系統是沱大便,根本不能做事嘛。嘿嘿,真是有趣!

有位開發人員名叫Aaron Boodman送給了Firefox一塊瑰寶,這人現在在Google,那瑰寶就是GreaseMonkey,提供了另一種方式來寫Firefox extensions,不像Firefox的外掛系統,GreaseMonkey的extensions可以被安裝且更新,不需要重啟系統,而且相當容易撰寫,這瑰寶替Firefox注入了新動力,我打賭大部分的Firefox開發人員(先不管使用者社群)都能了解並感激GreaseMonkey的重要性,因為它讓Firefox能長期存活下去。

有趣的是,GreaseMonkey本身是個Firefox外掛,但又提供它自己的外掛系統,這是外掛系統中常見的一個模式:有些外掛會成長變大,變得可組態化,最後可被視為獨立的程式系統;Emacs有很多這種外掛:advice套件就是一個好例子。順帶一提,Rails的filter系統也是以系統外掛的形式實作出來的。外掛(plug-ins),跟其他軟體系統一樣,也有生命週期,視乎它們納進多少我今天在談論的特質,它們最終都需要命令列模式、advice、擴充語言、等等,然而,外掛因為是立基於另一套系統之上,通常一開始就有這些東西。

打造可延伸擴充的系統比不能擴充的系統要更加困難,一般說來大約是三到五倍;在一開始就加入外掛系統比較容易,想在一套現存的系統上加進擴充能力,那可是夭壽困難的功夫啊,而且需要大規模地進行系統重構(refacotring),可不是Java圈圈流行的那種小小可自動化的重構,其需要花費的心力幾乎就跟重寫整套系統差不多,不過以重構的方式進行的話,可以靠縝密完善的unit tests來減低改錯的風險。

現今有很多軟體系統都只能透過網路遠端存取,例如經由瀏覽器與HTTP,一些建造這種系統的大公司們,包括Yahoo!、Amazon.com跟eBay,都開始意識到提供給程序員的擴充延展能力是他們系統永續存活的關鍵,他們開始釋出內部系統的部分介面給外界使用,通常以web service的方式,這提供了某程度的擴充性:讓外界獨立軟體開發人員可以打造特別的介面來存取系統;隨著時間演進,我認為在網路服務這塊領域分高下的主要分水嶺將取決於供程序員存取系統的介面的品質高低。

外掛系統要考慮到安全性,要考慮到易用性,要考慮命名空間,要考慮元件相依性,要考慮向後相容的問題,要搞一套就已經是該死的難了,更別提要搞的好;外掛絕對是軟體系統想要永續存活所需要的特質。

優良的軟體系統還需要哪些特性呢?

我認為有點很重要,至少在今天來說,偉大的軟體系統都有或是需要一支殺手級應用程式,每套世界級的軟體系統本質上就是一個平台,如果你有命令列模式、擴充語言跟advice以及外掛架構,那麼你有的就是平台的組成要素了,但你需要某理由來說服使用者們使用你的平台;所以,GIMP打出影像編輯這賣點,Eclipse是撰寫Java程式碼,Emacs是一般文字的編輯器,Rails是建造網站服務,Firefox是瀏覽網頁用的,Python是腳本語言用來驅動其他東西,Lua可用在嵌入式環境,Mathematica是數學運算,等等,它們也都可以被延展應用到其他領域,但它們在其利基領域中是最強的。

最泛用的軟體系統,我認為,是作業系統跟程式語言,即使如此,它們也都有個主要的領域,作業系統主要用於資源管理,大部分成功的程式語言通常都切割出一塊利基市場加以佔據:需要速度就用C++,做Unix系統管理時可用Perl,要用很肥大的API來嚇嚇客戶的話可以用Java,瀏覽器上幾乎都只能選用JavaScript,在Emacs上就只能用Lisp,還有,Ruby開始爆紅要歸功於Rails。

所以軟體系統要有個利基點。或許將來有一天,有套超級泛用的軟體系統,超級強大、設計超良好,以至於任何事情都適合用它來完成;或許會有那麼一天吧,或許你就是打造這套系統的人。

接下來是今天我要講的重要特質的最後一項,跟其他項一樣重要,就是優良的軟體系統是能夠自我檢視有內省能力(introspective),你可以在執行時到處戳戳看檢視系統的狀態,理想情況是由系統自己戳自己、自己檢視自己,至少要做基本的狀態監視,即使是一個已經有一堆靜態檢查的小小系統,你仍然需要監控,例如輸入與輸出佇列,對大系統來說,你什麼東西都需要做監控,包括監控系統本身。(若是不做這種監控系統的監控,可能會發生一件糟糕的事情,你的系統進入某種狀態,所有狀態檢查都OK,但系統實際上已經在擺爛了。)

內省機制(introspection) 可以(且應該)有各種不同的形式,不僅是狀態監控,系統管理工具與診斷工具也是一種內省,一步步除錯追蹤也是,效能分析也是,動態連結也是:系統必須能夠(舉例而言)執行bytecode verifiers以確保剛載入的程式碼能通過一些基本的安全性檢查。內省機制通常會降低效能,所以很多程序員不會在執行時做任何的內省檢測,或是極簡化地採用底層系統提供的內省機制(例,RTTI或Java的reflection),如果你用內省機制來換取速度,等於折掉你軟體數年甚至數十年的壽命,如果你是個顧問公司,或是你只想要讓系統可運作後就不管了,那麼或許這點不是太重要,但我認為大部分寫軟體人的都偏好在能長久永存的系統上做開發。

優質軟體共有的特質還有一長串可寫,但行文至此,我想最好談一下為什麼這些特質都根源於“毋須重新啟動”,之後遇到其他特質你就可以輕易地辨識出。

重新啟動等於死亡

今天我沒辦法很公平地談這個主題,部分原因是這個題目很大,部分原因是我還沒通徹搞懂,所以我最多只能把輪廓描繪出來,希望你能看出這問題完整的面貌。

首先,讓我們從比較哲學的觀點來看看,我用一種很廣義的觀點來看待軟體:大部分的軟體都不是人類創造的,那些人造的都是些大便,有很多相當棒的自然存在的軟體,例如人類大腦的運作,其大部分可以被看做是軟體,還有我們身體的組成運作很明確地是軟體。(見鬼了,我們甚至可以在DNA中看見組合語言指令碼,雖然還沒被完全解碼。)所以人們至少帶著兩套軟體系統:肉體與心靈。

我也認為穩定的生態系統也算是軟體系統,喔,順帶一提,穩定的政府也算,還有,人群組成的組織也是,例如你的公司;除非我整個誤解涂林的觀點,要不然軟體可以是任何能做運算的東西,而計算只需要一台有著幾個簡單機制的機器,這些機制是用來改變機器的狀態,包括條件式分支、跳躍指令、讀寫狀態、加上一組指令(程式)來指示該做哪些動作。

我假設我在這給軟體下的定義,是相當清楚且不證自明的, 所以,關於軟體並非皆人造這觀點,我就不用再多做解釋辯護了。

所以,我的第一個反對重新啟動的論點是,在自然界中可沒這回事,或更精準地說,真的發生的話後果都很悲慘;如果你不喜歡一個人的所作所為,你不能殺掉他們、修正DNA、然後從小孩再成長一次;如果你不喜歡政府,你不能說關就關、找出錯在哪、然後重新開始,那麼,為什麼我們幾乎都用這種方式開發軟體呢?

反對重新啟動的下一個論點,看看現在開發人員建造的過時舊軟體,把你寫程式時戴的眼鏡拿下來,以一般使用者的角度想想重新啟動這回事,你喜歡被迫使重新啟動軟體嗎?當然不囉,多不方便啊。

大約十年前,微軟下了個決定要把Windows NT中需要重開機的地方都修改掉,你還記得嗎?我從未在微軟工作過,也是輾轉得知這故事,所以我不是確切了解事情始末,聽說那時每個小小的組態改變都需要系統重開機,好像是某個高層忍不下去了,然後他就組織統整了很多人力來修改程式,當我聽到這軼聞時,已經減少到大約剩下五個地方需要重開機;想想這對美國財政的衝擊吧──數以百萬的人們每天省下5到30分鐘的時間等待Windows重開機,很巨大的影響對吧,如果他們能夠將藍天白雲死機畫面也改好,那這就是一套相當棒的軟體,對吧?

Linux在這方面也經歷了不少成長的苦痛,終於開始越來越好,但我猜今時今日它需要重開機的次數仍然比Windows還要多。

不論如何,重新啟動這問題不僅僅是帶來不便而已,應該還有更深的意涵,從一個比較激進但仍可加以辯護的觀點看來,一次重新啟動就等於一件謀殺案。

怎麼會呢?嗯,古今中外尚未解決的難題之一是關於意識(consciousness)的問題:這是個啥玩意?有意識代表著什麼?到底什麼叫做自我察覺(self-aware)?寵物有意識嗎?昆蟲呢?捕蠅草呢?分界線要畫在哪裡呢?

歷史上各個時代,關於意識的問題有時流行有時不流行,不過在過去數十年間這是個非常風行的話題,哲學家跟認知科學家正攜手合作試著找出解答,據我所知,主流的觀點(或是叫領先的理論,什麼都好啦)是這麼說的:意識是遞迴式的自我察覺與認知,而且是漸層式的(對比於簡單的開與關),很明顯的是一種的函數,複雜度視乎可以處理多少輸入以及大腦可以同時處理的自我察覺之遞迴階層的多寡而定。所以,寵物是有意識的,只不過不像人類那樣。至少這是我從過去三十多年所出版的書籍、研究報告和論文,所得到的結論。

舉個例,我過去一直把我的狗──奇諾(一隻獅子犬)──當做一台非常簡單的狀態機:噓噓、拉屎、睡、吃、玩、花數小時舔鼻子,但每當我更進一步了解他,就逐漸辨識出更多的狀態,有數百個之多,甚至數千個,但我依然尚未完全被說服他的行為不是決定性的(deterministic),我愛他愛到每一根毛我都愛,所以就假設他有懷疑的能力以及有自由意志(free will)吧,但不用太費心就可以有90%的準確率猜中他的行為;我想像這套用在我身上也同樣成立吧!

狗似乎比老鼠複雜多了,而老鼠應該比蟑螂複雜許多,一直循線追下去(或許是螞蟻?扁形蟲?單細胞有機體?),看起來到那時,動物的行為應該是具有完全可決定性的。

起初那說法對我很合理,但我現在的想法改了,我個人認為所有的生物都具有某些不可決定性(nondeterminism),其程度視乎他們的組成因素中軟體的範圍有多廣,硬體部分肯定是可決定性的,奇諾在出生後任何不用教就會做的事情不是硬體就是軔體(一種內建的軟體,無法移除或更改),進行尿尿的程序(以及何時該去)、打噴嚏跟其他反射動作、處理感官資訊、消化食物、以及其他上千個程序通通都是直接編碼在他的大腦軔體或身體軔體中,可被精準地預測其行為,任何觀察到的差異處都是因為軟體部份對當前環境做出的修正。

換句話說,我認為意識與自由意志(也就是不可決定性nondeterminism)兩者皆為軟體性質。

沒人會為殺死一隻阿米巴變形蟲而掉眼淚,呃,大部分的人不會,同樣的,當你的程式印出“Hello, World!”後就離開main()並消失的無影無蹤,也不會有人會心情激動,但我想我們已經建立起一個共識,每一次執行“Hello, World!”程式就會產生出一段短短的意識活動。

呃…有幾分意味啦,一支“Hello, World!”,沒有迴圈也沒有分支,不具有任何不可決定性(除非由外界強加上去,例如一個亂數產生的硬體錯誤),所以你可以把它當做純硬體,只不過是用軟體程式寫出來,但在“Hello, World!”跟Hal 9000之間應該存在著一支擁有初步意識能力的軟體程式,若是這樣的話,把它關掉就等同於殺掉他一樣。

我們今日的軟體有那樣的複雜度嗎?夠複雜而且有足夠的自我感知能力,從倫理道德的觀點來看可以被認為具有意識嗎?答案大概是否定的,但我認為總有一天我們會達到那個地步,到那時,我真心希望我們不是用今日的方法在開發軟體,也就是編譯加上重新啟動的開發循環週期。

我想今日我們建造出來的軟體,大部分都像是在玩骨牌,這叫做骨牌設計法,你非常小心排著骨牌,只能跑一次,所有的骨牌都依照排好可預測的順序倒下,而且如果需要的話你將所有骨牌重新排一遍;最終結果可以比玩骨牌更精美更棒──想想玩具總動員裡面的一張一張畫面,本質上並沒有太大的差異,但的確更精美。

每一套真的很複雜的軟體,像是搜尋引擎或是電子商務系統,通常都是用骨牌設計法建構出來的,如果你主要以C++或Java寫程式,幾乎一定是用這種方法。

重新啟動一套骨牌系統是不可避免的動作,每有改變每次升級都需要重新啟動這樣的概念,跟這些系統在我們心中的形態緊緊相連,對我而言,我不喜歡骨牌,我個人想要跟一支變形蟲玩,而且如果他拒絕學習怎麼走迷宮或其他事物的話,我會把他壓扁然後發現得到新型阿米巴,不過在那之前我會先多想個兩次。

DWIM and QWAN

我的思考能力到現在已經有點模糊了,所以我盡量長話短說。DWIM是個可愛的頭字縮寫,我想是由Larry Wall發明的,意思是“Do What I Mean”,Perl社群常常使用這個字眼。

DWIM背後的概念是完美的軟體就好像一個從瓶子跑出來且滿懷感激讓你許願的精靈,我不是在說那些你對著噴泉或流星所許的不值錢願望,大家都知道你必須注意許願時的遣詞用字,因為許願精靈會以字面的意思給你要的,而不是你真正想要的,就好像所有你在使用的軟體一樣;相對的,DWIM精靈站在你這邊,可容忍文法或邏輯上的錯誤,不會得到錯誤的結果。(“一個男的走進酒吧,帶著迷你鋼琴跟一把十二英吋的鋼琴家…”)

每個程式設計師表面上都試著把DWIM寫進他們的軟體內,但幾乎都是用臆測的方式,事先假設使用者的行為會如何如何,有時候是以協力過濾(collaborative filtering)或其他以規則演算的方式來寫,但同樣也是會錯誤百出;唯一能讓DWIM不再只是驚鴻一瞥的方法是創造一支真正有智能(intelligent)的軟體──不僅是有意識,而是有智能,希望也有智慧和感知能力,以及(咽口水)友善的。

但這就會令人憂心了,大家都知道“智能”並不必然對你會產生有同理心,特別是你做了一些白目的事情。

所以到底要不要DWIM啊?就一方面而言,我們希望軟體可以更好,預測你想要的,回應你的需求,所以你可以過著你一直渴望的快樂幸福的生活,就另一方面而言,想達到這樣的境界的唯一方法是打造比我們更聰明的軟體,到那時,好一點的話我們要擔心會不會被邊緣化,壞一點的情況是我們要擔心會不會被奴役或被消滅掉。

我認為這解釋了,至少某部分解釋了,為什麼我們業界大部分都在打造越來越大的死系統來達到假假的DWIM(而且,打造這種大而麻木的系統,有什麼語言會比Java更好嗎?),死系統很安全,它們是決定性的且完全可控制的。

讓我們野心先別那麼大,省下口水先假設在我們有生之年不可能建造出有意識、有智能的軟體,那麼,比起現今我們大多數人產出的一般般的大便,有可能建造出比那更好的軟體嗎?

有另外一隻精靈叫做QWAN,建築師Christopher Alexander所發明的詞彙,意思是“Quality Without a Name”,而且(就跟這篇論文的其他難懂的觀點一樣)這是他早就了解的概念,終其一生不斷嘗試想把它描述出來跟找方法確實把它打造出來;簡短一點講的話,QWAN是一種無形無影“看見了自然就懂了”的空間或結構的特質,帶有 (a) 經由有機成長可以達到的境界,以及 (b) 詭秘地跟人類大腦中某部分硬體或軔體有關係,因為每個人都會有那靈光一閃抓到那模糊意義的時刻,但沒人真的知道為什麼會這樣。

在過去晦暗的時代,一些軟體專家們領悟到軟體也可以有QWAN,然後又發現在軟體跟從實體結構中想把它揪出來都一樣困難。

我在這裡主張,在之前列出我喜愛的系統,剛剛好在這些系統中可以找到明顯的QWAN,而我說那些缺乏重要特質的那些系統,都沒有QWAN的蹤影。(我之前有列舉出幾個較明顯的例子,然後含糊帶過其餘的,還記得嗎。)

人們都看出Emacs擁有QWAN這特質:美好、有生氣、舒服、正確,好像舊舊的牛仔褲那麼合身,猶如圖書館內火爐旁舒適溫軟的椅子;我現在說的恰恰是非常偏右腦的東西,情愫與感性:剛剛好這正是搞軟體的人很憋腳不擅長的,難怪我們不知道怎麼把這玩意放進軟體內,但不像使用者介面設計,軟體QWAN只可能從程式設計師手底下出現,因為對那些在你的軟體平台上開發的人來說,你擔任的角色就猶如室內裝潢師、主廚、人體工學顧問一樣。

那就是為何我認為大部分的軟體都是大便的原因,我不是在講一般使用者的使用經驗與感受,我是在講開發人員的開發經驗與感受(如果有的話),設計是一種藝術,不是科學,而且芸芸眾生皆非天生的藝術家,所以當QWAN出現在軟體系統中,即使只有一點點,也是很非凡的成就。

注意,順帶一提,軟體系統給一般使用者的使用經驗跟給開發人員的開發感受根本就是兩回事,Visual Studio提供相當精緻的使用者經驗,在這裡我說的使用者是指寫C++或C#程式碼的程式設計師(相對的另一方是,寫工具軟體幫助其他人寫程式,或是幫助別人把一些常做的事情自動化);Emacs讓使用者超難上手,但給開發人員的介面無與倫比好的不得了;Eclipse在兩者之間,但看得出來比較偏向一般使用者那一邊。

最後,QWAN有某部分是無法言喻的,把我的配料(命令列模式、擴充語言、advice系統、等等)加進去會讓系統更好,但不保證QWAN就會浮現出來,QWAN那些部分最終還是要回歸到品味(taste),既然品味人人皆不同,而且QWAN遠遠比不上DWIM那麼聰明,一個人的QWAN或許會是另外一個人的惡夢。

不過我依然認為我們應該試著將它打造出來。

型別系統(type systems)扮演的角色

太好了,來龍去脈說的夠多,我終於可以解釋一下我對型別系統的看法。

簡單講,型別系統是用來建造硬體的。每套軟體系統都有台機器在底下運轉──其實應該說有一整疊的機器,很多很多,從馮紐曼機器的CPU與暫存器與匯流排,往下到半導體元件,再到原子與夸克,以及量子力學的理論基礎。

不過我對硬體有更廣義的定義,硬體可以是任何事物,可以產出決定性的運算結果,非常硬而沒彈性,你必須先把它弄破才能做點修改要不然就要整個換新。所以,它並不只是由原子組成物理結構意義上的硬體;就我的觀點看來,一台機器的硬體部分也包含了那些需要重新啟動才能做修改的軟體。

型別系統的概念跟“編譯期間”與“靜態檢查”緊緊相連,我對型別系統也有非常廣的定義,我認為所有加諸在運算上的限制都是某種型別系統,包括scoping跟visibility(public/private/protected/friend/...)的限制,安全性限制,甚至程式的基本運算架構也算,我的看法中的“靜態”型別檢查其實包括了某些發生在執行週期的檢查。

但當談到靜態型別系統時,大部分人指的都是編譯週期的檢查與驗證(語言的語法中的型別標示,從AST建立推論出來的型別資訊),這些是在執行時就會不見了的東西。

跟把限制與檢查延到執行時才做相比較,靜態型別系統有一些優點,其一,程式碼跑的比較快,因為如果你執行前就做好檢查的動作,而且你若能保證執行時程式不會有改變,那你就不必在執行時做檢查,另外一個,靜態的限制條件,通常都可以用編譯器之外的工具加以分析(例如你的IDE),這可以幫助你了解程式執行的流程為何。

真是太棒啦。

靜態型別系統沒什麼不好,只是你必須知道,當你用它時,你是在建造硬體,而非軟體。

對一個寫好的程式而言,靜態型別系統絕不會對執行效能有所影響:不論系統提供多少的型別可用,產生出來的機械碼是一樣的,有個恰當的例子:C就只有那麼一點點最基本的型別,但C++程式不會比C跑的更快,C沒有動態型別檢查(dynamically typed),但也不太能算是靜態型別,單單用C也可以寫出又快又穩的程式。(Emacs跟Linux核心就是兩個好典範。)

靜態型別系統的正確使用時機是當我們想把軟體固定下來變成硬體的時候;每當一組運算法則(例如OpenGL繪圖指令)變得很普遍很穩定時,就值得犧牲一些彈性來將它轉到機械層以獲得較高的效能,也就是轉成硬體,但通常效能最佳化會對彈性造成很大的損害(其中非常小的一部分是標準化過程所帶來的,我想)。

因為眼下大部分的程序員都偏愛打造牽線木偶而不是會讓人恐慌的真人小孩,大部分的程式設計傾向於建構一層又一層的硬體;C++跟Java程式設計師們(我想C#也是)基本上在一開始就用型別系統來塑模任何問題,也就等於把每一行程式都轉成硬體的形式。

僅用陣列、鏈結串列、雜湊、樹結構與函式就可以用C++跟Java寫出鬆散型別檢查(loosely-typed)的程式,但這樣會被兩個陣營的人強烈抨擊(或至少皺眉不屑),在兩個陣營裡,嚴格型別檢查跟詳細塑模設計在過去十年來逐漸成長成主流,他們認為型別檢查鬆散的程式會危害生產力(也就是指會影響開發時程),也會危害彈性(也是指會影響時程表,因為沒彈性表示很難納進新的功能)。

Hindley-Milner型別系統(源於學界,幾乎打不進業界)的虔誠教徒相信H-M型別系統遠遠優於Java或C++的,因為(理由之一)它漏洞較少。

H-M不被業界所使用,各位,聽我說,正是因為它沒有任何漏洞。如果你正在開發軟體,但你相信(同大多數人)開發方法是在一個長又痛苦的循環中不斷地建造硬體然後丟棄,直到那塊硬體是你想要的東西才停止,那麼有時候你會需要掙脫型別系統的限制:告訴型別系統閉上鳥嘴、我知道我在做什麼、我的程式寫的沒錯正是我想要的,型別轉型(type casts)、縮減或放寬的轉換、用friend functions來繞過類別的存取保護機制、把自訂的迷你語言放進字串內然後自己手動解析,有數十種方法可以跳過Java跟C++的型別系統檢查機制,而且程式設計師們一直都有在用,因為(很少人知道這點)他們真正試著建造的是軟體,並不是硬體。

以沒路用的形式上的數學觀點看來,H-M是很優美的,它處理起某些運算結構與概念特別巧妙;在Haskell、SML、OCaml可以找到的樣式比對後分發(pattern matching dispatch)非常非常好用,但不意外地,它處理起其他一般常見且大家都需要用的運算結構與概念非常笨拙,不過那些擁護者反而辯解說成是你錯了、其實你不需要這些東西,例如像是,你知道的,設定變數這種基本動作,你才不想要這種東西咧,相信他們吧。(OCaml讓你做,但提到這點時他們的目光都下垂看著自己的鼻子。)

沒錯,加上一點努力,你可以用Haskell打造出漂亮柔軟的牽線木偶,但永遠就是個小木偶皮諾丘,絕不會有好心腸精靈讓你美夢成真變成小男孩,Haskell中幾乎沒有動態程式碼載入或是執行週期的反射機制(runtime reflection)這些東西;這些概念對他們來說太怪異了,以至於連想跟他們討論這些概念為什麼有用都很困難:他們的世界觀裡沒有軟體是活生生的可以成長可以呼吸這樣的思想。

讓我們來看看動態型別檢查的天地裡稍微不那麼體面的語言,大家都知道很多程序員都把動態語言跟玩具看做同一類;主要流行的動態語言包括Perl、PHP、Python、Visual Basic、JavaScript、Ruby、Tcl跟Lua,除了這些語言設計者應得的尊敬之外,我不得不說他們在學生時代的編譯器課程應該是被當掉的吧,舉個例:他們都沒弄好lexical scoping;諷刺的是,他們的腳本語言放到鎂光燈下後反而讓他們(Larry, Guido, Matz, Brendan, ...)搖身一變成為世界級的語言設計者,這也讓他們,跟Salieri一樣,能夠以大部分人不了解的觀點來理解領會他們設計語言的不足與弱點(現在想修正已經太太困難了)。

Scheme大有前途,但它有個致命缺陷,就是它不能成長,跟已被業界廣泛採用的語言與平台情況不同,它們是為了保住廣大的使用群,Scheme是為了維持小而美才能佔據它的利基市場,也就是用在電腦科學教學課程上。

多方看來Common Lisp是個理想的選擇:它是動態型別檢查的語言,但可選擇性地加上型態標記(換句話說,你建造軟體,然後可以選擇要不要轉成硬體 ),很多好工具可用,說明文件也都有,具有好軟體之必備特質,我在這篇文章舉出來的它都有,而且還有相當程度的QWAN,然而,它已經停止繼續發展了,程序員就像鯊魚嗅到一樣敏感察覺到它沒氣勢了,Common Lisp擁有的能量跟William F. Buckley, Jr.和Charleton Heston激辯不相上下。(我看過一次,我發誓他們兩個一定有一半時間在打呼)。


單純的Lisp缺少了C的語法結構,在C語法根絕之前:這項任務可能要花上五十年,我們絕不可能在電腦運算與語言設計上有什麼真正的進展,在那之前若想往前走,唯一之道是把程式語言中的模型(AST)與表示(語法)分離開來,允許語法外衣,讓愛用者們繼續用C語法直到他們全死光,Common Lisp本可以加進額外的語法規則,但它既然已不再成長,也就不可能發生。我會到別處尋找下一個主流物(the Next Big Thing)。

結論


時間是凌晨三點三十分,已經超出我每月部落格寫作限制時間兩小時了,你下個月或許會在我這看到一篇簡短的部落格!不論如何,我想我已經把胸中的不吐不快都寫下來了。


我想,我們的理想是軟體能夠按照你的意思執行動作,不過因為DWIM只可能發生在真正有智能的軟體上,而想要依據變動中的世界來預測軟體該怎麼寫太困難了,話雖如此,我認為那是我們想要的。

我懷疑短時間內不可能有那樣的東西出現,因為大部分的程序員一股腦地專注在打造硬體上,勤奮地將型別系統使用在他們的軟體上,太過度了反而搞壞了原本還不錯的軟體。

在魔法油燈變出DWIM前,我認為我們應該建造有生命的軟體,或許只是像樹一樣的生物,但那就足以喚醒其體內的QWAN,任何只要有一丁點的QWAN的系統都真的很棒、值得使用──至少對寫程式的人來說;一般使用者對這些系統對評價參差不齊。

有生命的軟體,擁有命令列模式,因為你需要能像個成年人一樣跟他說話,擁有擴充語言,因為要能夠幫助他成長,擁有advice系統,因為你需要訓練跟修正他,擁有一塊利基市場,因為他需要使用者們才能成長茁壯,擁有外掛系統,這樣你才能幫他打扮參加舞會,而且他是有自我察覺(self-aware)能力的,能力的最大值只會受限於外在效能的約束,這些特質都必須巧妙優雅地整合在一起,花在每個子系統上的心力與詳細程度都跟整個系統一樣。

而且你不應該在每當需要修改時就要謀殺它一遍,如果你對待你的軟體像個活跳跳的生物,那麼最終他就會開始變得像個真的。

如果我們能喚醒真的人工智慧,我想皮諾丘是個適合的名字。

2010/10/11

讀後推薦:程式設計師的自我修養:連結、載入、程式庫 by 俞甲子/石凡/潘愛民

我看了這本書,覺得還不錯,對我很有幫助,釐清了很多概念與疑問。

書名:程式設計師的自我修養:連結、載入、程式庫
作者:俞甲子/石凡/潘愛民
出版社:碁峯




史瑞克說妖怪像洋蔥一樣,是有層次的,資訊科學這玩意兒更是如此,從上到下不知有多少層,有應用軟體層、有整合開發套件跟永遠看不完的類別庫與函式庫、有作業系統提供的API跟系統呼叫、有軔體層、有硬體層,每層都還可以繼續細分下去,另外還有網路服務跟雲端運算,各種數也數不清的結構與架構,嘿嘿,看來咱們比妖怪更有層次啊。




這本書的主旨就是副標題所說的,關於軟體程式從寫好開始,編譯成組合語言,組譯成機器指令,加上執行階段程式庫靜態連結成執行檔,執行時由作業系統載入到記憶體裡,動態載入與連結所需的程式庫,這一連串的過程;這本書解說了在x86架構下的ELF檔與PE檔格式,分別是在Linux與Windows系統下所使用的檔案格式,用來儲存執行檔(無附檔名或.exe)、中間目的檔(.o or .obj)、靜態程式庫(.a or .lib)、動態程式庫(.so or .dll)等等,書中有概念的闡述,也分解簡單的小程式來當實例,對我來說,幫助我釐清了很多觀念,例如什麼叫做runtime(runtime environment, runtime library, 執行階段程式庫)、編譯器之後的靜態連結器要處理哪些東西、動態程式庫(.so or .dll)背後的道理是什麼、符號表(symbol table)到底有多重要、在main()執行之前作業系統做了哪些事情、等等,真是太棒了,好書一本。



有一些書,偏重理論與概念,例如鐵甲武士惡鬥吐火龍,對於一個在應用軟體層的人來說,較不實用,我想要的是實際上用的工具與技術(compiler, dynamic linking, etc..)的概略說明,我想要的是當前廣泛使用的作業系統(Linux, Windows)與工具(gcc, Visual C++)與檔案格式(ELF, PE)的探討,當理論書籍說有哪些方法可以用來實作出什麼什麼技術,本書則告訴你Linux與Windows用的技術是什麼;這樣當我在其上開發軟體時,才不會被華麗文藻包裝過的說明文件迷惑,搞不懂共用物件與plug-in的差別。

PS 不要誤會,龍書是經典,編譯器也是資訊科學必修的課程。



還記得當初寫第一支程式的時候嗎?從寫好到執行看到螢幕上印出hello, world.,其實中間經過了許許多多的關卡,編譯器、組譯器、靜態連結器、作業系統載入、動態連結、虛擬記憶體系統、等等,不說不知道,稍稍往下鑽研就會被嚇到,疊床架屋一層又一層的技術,才能讓我們鍵入少少幾行程式按下個按鈕就可以寫出hello, world.,當我們在其上享受各種新式技術與便利之餘,也別忘了要了解一下底層的原理,因為三不五時,底層的東西會跑上來嚇人,有時候程式當的亂七八糟,可是又看不懂錯誤訊息,有時候甚至連錯誤訊息都沒有,這時若心理有底層技術的概念,解決問題的機會就會多個幾趴。




如作者所言,關於載入器(loader)與連結器(linker)的資料是不多的,就算有也是那種看起來很硬很技術性的著作,有這麼一本算是研究心得的著作,要謝謝作者所花的心力。

2010/09/07

翻譯:Windows程式設計的革命簡史(A Brief History of Windows Programming Revolutions) by Ron Burk

文章:A Brief History of Windows Programming Revolutions(Windows程式設計的革命簡史)
日期:2009.12.01
作者:Ron Burk
作者的部落格:Ron Burk - Programming, psychology, and "stuff".
作者簡介:
曾擔任Windows Developer's Journal的編輯職務;正在寫一本書,書名為"The Pop Psychology of Programming";這裡有一段5分鐘的影片,Ron Burk講解三種computer programming的定義。



Windows程式設計的革命簡史

爬出DLL地獄──再一次


首先,我們有Windows API以及DLL地獄(DLL Hell),第一波的革命技術是DDE──還記得熱連結(hot links)讓我們可以產生出狀態列來顯示微軟股票目前價位嗎?

大約就在那時,微軟搞出了VERSIONINFO這玩意,用來揮除DLL地獄這夢靨,但微軟的另一個團隊發現DDE有一個致命性的缺陷:那不是他們發明的!

為了解決這個問題,他們創造出OLE(跟DDE差不多,看起來不同而已),而且我還深深記得在一場微軟討論會上,有位演講者公開聲明Windows API很快就會改寫成OLE API,螢幕上的每個控制元件將會變成OCX;OLE引進介面這玩意,用來跟DLL地獄說掰掰,還記得那"就地(in situ)"狂熱嗎,還記得那時我們幻想著有一天所有應用軟體通通內嵌在一支(很明顯地,非常大的一支)Word文件中嗎?

就在那時期左右,微軟改拜C++,MFC也出來了,再一次地解決老問題,但這一次有繼承(inheritance)這玩意,不過咧,OLE可不會坐著發呆,重新包裝後以COM之名現身,突然我們醒悟了,OLE(還是DDE啊?)才是王道啊──裡面包含了精心打造的元件版本系統,是用來避免陷入DLL地獄的,與此同時,微軟某搗蛋團隊發現MFC有一個致命性的缺陷:那不是他們發明的!

他們毫不猶豫地修正了那缺陷,創造出ATL,跟MFC差不多,看起來不同而已,並且試著把COM團隊費盡心思教會我們的技術細節通通藏起來,這可刺激到了COM團隊(還是OLE啊,搞不清楚?)重新改名為ActiveX後再出發,釋出數以百磅計的新介面(甚至有新的版本控管介面,用來防止DLL地獄的玩意),還有,讓我們的程式碼可經由瀏覽器來下載的能力,加上各種病毒(哈哈──想辦法跟上腳步吧,你們這群ATL傢伙們!);就像個被忽視沒人裡的小孩一樣,作業系統團隊大聲呼喊著看過來喔,"準備好等著迎接Cairo吧",ㄘㄟˊ,他們從來沒能好好解釋說明過那是啥東西,更別提出貨見人了,不過有一點功勞要算在他們身上,作業系統團隊確實導入了"系統檔案保護機制(System File Protection)",是用來遠離DLL地獄的玩意兒。

與此同時,微軟另一支團隊發現Java有一個致命性的缺陷:那不是他們發明的!怎麼補救呢?於是有了J、Jole、ActiveJ(老實說,我記不得名字了),跟Java差不多,看起來不一樣而已,很興奮吧;不過昇陽開始告微軟,用的是某古老的法條說一家公司不能在一年內放出太多的技術,很明顯地是在試著阻止微軟創造雷同但只是看起來不一樣的產品,結果是讓微軟有了全新的自由,可以把錢塞進國會議員的褲子裡;還記得J/Jole/ActiveJ的產品經理用他的鞋拍桌保證微軟絕對不會終止放棄他的產品嗎?傻孩子!這一切只代表了一件事──ActiveX(還是應該叫COM啊?)團隊得到的關愛眼神太少了,這一群身段柔軟的API打造者,以更強的姿態COM+(不是應該叫ActiveX+嗎?)還有MTS(我不懂,為什麼"MTS"裡沒有"COM"或"ActiveX"或"X"或"+"等字眼呢──這次我整個被他們嚇到了!)回來了,他們還威脅說不久之後就要在所有術語後面再加上一個"+";大約在那個時候,某人大聲嚷嚷著"Windows DNA"和"Windows Washboard"一段時間,不過在我搞懂之前就死掉出局了。

那時,微軟注視著網際網路(internet)的發展已有數年,越來越不安,他們發現,網際網路有個致命性的缺陷:哎呀呀,你大概知道我要說什麼了,於是把我們帶到.NET(唸起來像是"doughnut甜甜圈",看起來不一樣而已),跟網際網路雷同,只不過有著更多的文宣廣告與宣傳;讓我把一點說的非常非常清楚:.NET解掉了DLL地獄這個麻煩,.NET有一個新的程式語言,叫做C#(發現Active++Jspresso有個致命性的缺陷,所以下場是出局了),.NET包含一個虛擬執行機器,所有語言都在上面跑(發現依賴英代爾CPU有個致命性的缺陷),.NET只用一套登入系統(如果不把你所有的密碼都存放在微軟的主機上,那是有致命性的缺陷的),事實上,比較簡單的方式是把.NET沒有的東西列出來;毫無疑問的,.NET將會是Windows程式設計的革命性的徹底改革的新方式...到明年就不是了。

2010/08/22

翻譯:C++的編譯速度(C++ Compilation Speed) by Walter Bright

文章:C++ Compilation Speed(C++的編譯速度)
日期:2010.08.17
作者:Walter Bright
作者的部落格:Walter Bright Home Page
作者簡介:
Walter Bright是位電腦程式設計師,是D語言的設計者,第一套C++原生編譯器的主要開發員,也就是Zortech C++(後來變成Symantec C++,現在是Digital Mars C++),在C++編譯器之前,他開發了Datalight C編譯器,先以Zorland C後以Zortech C之名販售。



C++的編譯速度


我常聽到有人抱怨說C++程式碼編譯速度很慢,有時候甚至要花上整夜的時間,編譯慢是exported templates這玩意的源由之一,甚至列在發展Go語言的理由清單上,這點確實是個問題;既然我身在C++編譯器的產業中,三不五時就會被問到這點。

為什麼C++編譯速度慢?一旦我們合理假設開發C++編譯器的人都擅長於寫出效能高的程式碼,那麼,一定有某個深植於C++語言本身的原因;的確,不同牌的C++編譯器速度快慢相差極大,但還沒完喔,其他語言通常能夠快上一整個等級,而且厲害的編譯器專家應該不會只為其他語言寫編譯器吧(!)。

我從1987就在寫C++編譯器了,對比今日想當年,電腦可是慢的不得了,所以我投注極大的精神讓編譯器能夠快一點,花上大把的時間做效能分析以及微調各個小地方來讓編譯器更快,我發現,語言本身的某些特性讓編譯速度快不了。

理由是:


1. 轉譯過程中有七個階段[1]。雖然有些可被合併處理,但在前端處理原始碼最少要有三個階段,至少我還沒找到降到三以下的法子。要快的話語言設計時就只能有一個階段,C++0x惡化了這點,居然要求trigraph轉換與行尾為\與下行接合這兩個功能要能夠支援string literals[2]。

2. 每個階段都相依於前一個階段,意思是,沒有可靠的方式可以做往前先看的動作,例如,往前先去找#include然後先去讀進檔案;編譯器沒辦法往前先看出是個string literal所以不要做trigraph轉換,必須先做trigraph轉換,但要做好回到上一步的準備;我從沒找出能夠平行編譯C++程式碼的方法,除了在make時加上-j參數這種很粗略的作法。

3. 因為#include是種文本逐字置入(textual insertion)的機制,而不是符號式(symbolic)的,當一個檔案被#include很多次,編譯器只能悲情地做白工地一再地處理,即使是被#ifndef包起來也一樣。(Kenneth Boyd跟我說,如果將標準文件讀的仔細一點,是有可能允許編譯器省略被#ifndef包起來的#include,但我不知道有哪一支編譯器利用了這點。)

4. 程式檔案中總是傾向於,通通#include進來就對了,當責任全部落在編譯器身上時,每一個.cpp檔通常都會連帶引出一拖拉庫的檔案要處理,在Ubuntu上,就算只把標準函式庫#include進來,居然需要處理74支檔案總共37,687行耶(不包括同支檔案被#include多次的情形);templates以及generic programming的興起更惡化了這點,而且,把更多的程式碼放進標頭檔(header files)中的壓力也逐漸升高,更是雪上加霜。

5. 語意上的與語法上的(不只是詞彙上的)處理單位依賴處在它之前的整個原始文本,意思是,沒有東西是上下文無關的;不把#include的東西先看一看,就不能正確地解析(parse)檔案,甚至是先做lex的動作也不行,標頭檔在第二次#include時可能含有不一樣的內容(事實上,確有標頭檔利用這點)。

譯註:語意上的 semantic,語法上的 syntactic,詞彙上的 lexical。

6. 因為第5點,編譯器在某個TU[3]所編譯的#include的結果,不能下一個TU共用,每個TU都必須從頭開始。

7. 因為不同的TU之間彼此不知道對方的存在,常用的templates在每個TU都會被產生出來,鏈結器(linker)會將重複的刪除,但當初所花的時間都白費了。

預先編譯標頭檔(precompiled headers)解決了一些問題,但那是對非標準的C++做出某些簡化後的假設,才可辦到,例如,標頭檔被#include還是會含有同樣的內容;所以你必須小心,不能違反這些假設。

想解決這些問題又要跟舊有的程式碼保持相容性,真是高難度的挑戰啊,我預期在C++0x之後,會有相當份量的心力花在這些問題上,但那至少是10年後了。

在那之前,並沒有哪個方法可稱得上是解答,exported templates被廢棄了,precompiled headers是不符合標準的,imports被踢出C++0x標準之外,以及往往你沒有選擇編譯器的權力,諸如此類的;現在來說,有效地使用make -j參數可算得上是最好的方法了。

我會再談談關於語言的設計,哪些特性能夠導致快速的編譯速度。


註解

[1] C++98標準文件的2.1章節, 七個階段是:

1. Trigraph與萬國碼轉換。
2. 行尾為\時接到下一行
3. 轉換成預先處理的標記(preprocessing tokens),標準文件註明說這是上下文相依的。
4. 預先處理的指令執行,展開巨集,#include的讀取以及再跑一次1到4。
5. 將原始碼中處在char與string literals的字元轉換成執行字元集(execution character set)。
6. string literal的接合。
7. 將預先處理的標記轉成C++的標記(C++ tokens)。

[2] 在C++0x標準文件中的例子在2.14.5-4:

const char *p = R"(a\
b
c)";
assert(std::strcmp(p, "a\\\nb\nc") == 0);

[3] 一個TU,也就是一個轉譯單位(Translation Unit),通常就是一支C++原始碼檔案,通常是以.cpp為副檔名,編譯一支TU會生出一個目的檔(object file),每個TU的編譯過程都與其他TU不相關,最後由鏈結器(linker)將目的檔整合成單一的執行檔。


感謝

感謝Andrei Alexandrescu、Jason House、Brad Roberts以及Eric Niebler給予這份文章草稿時的有用建議。

2010/08/16

翻譯:Java簡史(A Brief History of Java) by Jeff Foster

文章:A Brief History of Java(Java簡史)
日期:2010.08.13
作者:Jeff Foster
作者的部落格:Fatvat : Exploring functional programming


Java簡史

話說當年1995之時,昇陽(Sun)公司釋出Java程式語言,做為整個Java平台策略的一部分,當初喊著“一次編寫,到處運行”的口號,要讓Java遍地開花,從腕錶到手機到筆電到超級電腦,隨處皆可運行。



剛開始時Java的接受度有高有低,當出現Java力有未逮之處,就會有福音天使出來保證這技術將會改變世界,你看,靠Java運作的烤吐司機就在不遠的角落了。

隨著時間演進,為了守住“一次編寫,到處運行”的承諾,Java也逐漸背上更多的負擔,標記為廢棄的方法(deprecated methods)到處可見,但昇陽必須留下這些功能來保持向後相容性;java.util.DateTime此套件變成了劣質設計的同義字,差勁的命名規則總是在修正中(叫它size好還是length好呢?)。

微軟(Microsoft) 的.NET開始蠶食Java的領地,微軟團隊導入delegates,一種有做型別確認的函式指標,使得事件處理(event handling)變得容易許多;Java要趕快反擊,所以在版本1.1時,Java生出了inner classes,能夠有類似的效用,但以一種較有限制與用起來較麻煩的方式出現,一份Java白皮書斷定地說『bound method references是不必要的東西....這玩意減損了Java語言的簡單性與一致性』,但與此同時,將bound method references放進Java裡的勢力從沒消失過。

當Java到了版本1.4時,為了對抗微軟的.NET,昇陽決定要有一套新的策略手段,思索良久之後,昇陽將版本1.4改成“5”,試圖一舉超前.NET的2.0。

同時間附帶了另一個決策,實作generics,一種能夠有額外的型別安全檢查的技術,不幸的是,隨型別安全而來的代價是要多打字,工程師引入generics後,每次又要寫一遍List<Foo> foos = new ArrayList<Foo>敘述時,就會常被聽到咒罵聲。




大學很快地就接納了Java;學生們不再需要學習手動記憶體管理與指標的種種艱澀難懂的奧祕,反之,他們可以依賴Java來做這些苦功夫,集中精神在解決問題上,不幸的是,這也導致一批同等級的開發者,被稱作“設計模式使徒(Patternistas)”,手上只有鎚子且把所有問題都看做釘子去敲,在他們的領導下,Java命名規則越來越可笑,當這種類別名“RequestProcessorFactoryFactory”成為一種常態時,一些開發人員開始質疑無限制地構築抽象化高塔是否真為明智之舉。

當開發人員意識到他們終日所為僅是把數千行的程式碼搬來搬去而已,他們需要一個字眼來辯護他們的存在意義,那個字就是“重構(refactoring)”,所有的設計模式使徒欣喜不已;他們不但能運用工廠的工廠模式(factory factories)、獨身模式(singletons)與訪問者模式(visitors)來解決問題,而且還能朝三暮四改變心意,並用個時髦字眼來正當合理化!

整個產業的演進方向只是為了滿足設計模式使徒們而已,跟別人與別的語言比起來,老鳥們覺得Java越來越弱,所以新型開發環境出現了,IntelliJ與Eclipse的目標是將開發人員受的傷害最小化,靠著極佳的程式碼自動補足機制(code completion)與重構功能,只需按幾個鍵而不用輸入一長串的程式碼來達到那想要卻沒有的語言功能。




Java開始向企業端進攻,招攬了一些有領導地位的的架構大師,打造出一套會造成典範轉移的協同式架構方法來建置企業軟體,其結果不小於一場革命;爪哇豆(beans)現身了,Beans顯然是一種伺服端的元件架構,用來當企業應用軟體的建構基本模組。

使用generics所帶來的角括號障礙一旦減低之後,Java邁步向前跳上了XML的列車,藉由使用XML,開發人員就能將簡單明瞭的概念表達成一整個龐大囉嗦的角角惡夢,好處是XML檔案(不同於其他檔案類型)可以輕易地被電腦看懂處理,至於對人難以閱讀這種小代價是值得的,像是Ant和JBoss這些軟體都是由可執行的XML所建構出來的。

與此同時,在這些架構大師的宇宙之外,一群新種程式員認為將該死的工作完成遠比一整天都花在打字重要,這種想法誕生出一些框架,例如Rails,設計的重點在於不擋路讓你能解決真正的問題,其中心思路以“約定俗成先於設定(convetion over configuration)”之名廣為人知,Rails獲得正面的迴響,而且至少有一些Java信徒轉投向Ruby的陣營;Java不再能緊抓人心的第一個徵兆開始浮上檯面。

2006年8月,Java 7計畫啟動,很多開發人員都要求一個叫做lambda expressions的功能,毫無疑問地會簡化很多原先Java搞的很痛苦的撰碼作業,可惜,Java委員會四年後還在為這項功能的細微差別爭辯不休,而且有可能會被排除掉,Java 7的無作為引出了新一代更迷人的語言,例如Clojure和Scala,設計在Java環境中運行,但不需要Java語言。

2009年4月,甲骨文(Oracle)公司宣布計畫將收購昇陽,此乃敲下棺材釘的最後一鎚,由“黑暗王子拉瑞”所領軍的甲骨文是一部併購機器,擅長於企業軟體與賺進大筆鈔票,當甲骨文的律師們了解一份份的軟體專利文件,他們會挑個大目標然後引起戰鬥,而還有哪個目標比谷歌(Google)更大呢(一家網路廣告的領導品牌),所以甲骨文的律師猛撲而出,戰爭就此展開。

這會給Java帶來什麼後果呢?從15年前的小小開端起,在昇陽的領導下,Java爬上了最熱門的程式語言的頂端,而在甲骨文的控制下,不但到底會不會有下一版的Java情況不明,更別提開發人員渴望許久的功能了,這是Java的新開始還是結束呢?

2010/06/25

Lisp + GNU Emacs + SLIME on Windows

上一篇講了GNU Emacs on Windows,其主要目的是為了這篇,安裝lisp系統與SLIME,on Windows。

首先是安裝lisp系統的部份,如果要從原始碼自行編譯的話,那會較困難,所以我通通使用已經打包好的套件。雖然只要一套實作即可,但我安裝了三個(CLISP、SBCL、CCL),這是為了試試看比較不同實作的差異,你不一定要這樣。

下面的鏈結是我使用的版本,你可以造訪官方網頁看看有沒有較新的版本。

首先是目錄的問題,太長或有空格都有可能會出問題,因為Emacs、lisp都有很深的unix背景,所以我新增了這些目錄:
C:\home
C:\home\bin
C:\home\bin\emacs (GNU Emacs放在這)
C:\home\yehnan (我的使用者家目錄)

開始下載安裝吧。

第一:CLISP (GNU CLISP)
在網頁上找到給Windows使用的套件,clisp-2.48-win32-mingw-big.exe(3.9 MB)。
我安裝在C:\home\bin\clisp下。安裝套件會增加這個路徑到環境變數PATH中。

有個小問題,安裝後執行,“找不到svm.dll”,解決方法是把libsvm\svm.dll拷貝到full\下即可,原因大概是Windows沒有symboloc link這種東西。

註:clisp-2.49-win32-mingw-big.exe修正了這個小問題。

執行後看到這個畫面就表示成功了。


第二:SBCL (Steel Bank Common Lisp,從CMUCL分支出來)
在網頁上找到給Windows使用的套件,sbcl-1.0.37-x86-windows-binary.msi(10 MB)。
我安裝在C:\home\bin\sbcl下。安裝套件會增加這個路徑到環境變數PATH中,並且新增一個環境變數SBCL_HOME指向這個路徑。

執行後看到這個畫面就表示成功了。

第三:CCL (Clozure CL,前身為OpenMCL)
在網頁上找到給Windows使用的壓縮檔,ccl-1.5-windowsx86.zip(34 MB)。
我解壓縮安裝在C:\home\bin\ccl下。

執行C:\home\bin\ccl\wx86cl.exe後得到這個畫面就表示成功了。

哇,很簡單嘛,lisp系統的部份已經搞定了。接下來就是安裝與設定SLIME。


SLIME全名為Superior Lisp Interaction Mode for Emacs,顧名思義,是一個Emacs的擴充套件(外掛),專門用來輔助寫lisp程式。包括一個slime-mode(minor mode)用來增強Emacs原有的lisp-mode,一個比較好用的REPL(read-eval-print-loop),SLDB(debugger),以及其他輔助工具。

怎麼取得呢?要用cvs指令,最好不要抓取一些網路上的壓縮檔(官方或非官方),因為那些可能都太舊了。
cvs -d  :pserver:anonymous:anonymous@common-lisp.net:/project/slime/cvsroot co  slime
怎麼安裝呢?把上面取得的整個slime目錄放進Emacs的load-path有設定的地方,我放在C:\home\bin\emacs\site-lisp\slime\。

怎麼設定呢?這就困難了。
首先在C:\home\bin\emacs\site-lisp\新增檔案site-start.el,內容是
(setenv "HOME" "C:/home/yehnan/")
。因為Emacs預設的家目錄會是類似這種的C:\Documents and Settings\Username\Application Data,太長且有空格,所以改掉。

然後是在C:\home\yehnan\新增檔案.emacs,各種設定都會放在這裡。什麼,你沒辦法新增一個開頭是小數點"."的檔案?使用Emacs新增吧,其實Windows是容許這種檔名的。然後編輯,加入以下的內容:
;pathname of Lisp executable file, to run an inferior lisp process
(setq inferior-lisp-program "C:/home/bin/clisp/full/lisp.exe -B C:/home/bin/clisp/full -M C:/home/bin/clisp/full/lispinit.mem -ansi -q")

(add-to-list 'load-path "C:/home/bin/emacs/site-lisp/slime/")
(require 'slime)
(slime-setup '(slime-fancy))

以分號開頭的是註解。
inferior-lisp-program那一行,是設定你的lisp的執行檔與參數。參數是從網路上找來的。
load-path那一行,將slime的路徑加入。
require跟slime-setup那兩行,是要Emacs載入SLIME。詳情可看這裡。

怎麼執行呢?在Emacs下鍵入M-x slime(M代表Alt鍵,M-x表示按著Alt不放,然後按x,然後輸入slime,按Enter)。如果沒問題,應該就會看到以下的畫面。

這是SLIME的REPL,你可以看到buffer的名字是*slime-repl clisp*,在這裡就可以輸入各種lisp expression並作evaluation了,REPL的指令請看這裡。

剛剛執行M-x slime,slime會進行以下的動作,先啟動在.emacs設定的lisp系統(在Emacs->Buffers會看到一個*inferior-lisp*);還有一個叫做"Swank"的server,並建立起Swank跟inferior lisp之間的連線,然後開啟一個REPL。

恭喜你,已經有完整的lisp開發環境了。

對了,剛剛不是裝了三套lisp系統嗎?
請在.emacs把inferior-lisp-program那一行換成這個:
(setq slime-lisp-implementations
'((clisp ("c:/home/bin/clisp/full/lisp.exe" "-B" "c:/home/bin/clisp/full" "-M" "c:/home/bin/clisp/full/lispinit.mem" "-ansi" "-q"))
(sbcl ("sbcl"))
(ccl ("C:/home/bin/ccl/wx86cl.exe"))
))

如果直接打M-x slime,預設會執行第一個lisp系統,也就是clisp。
如果打M-- M-x slime,然後輸入clisp or sbcl or ccl,就會執行指定的lisp系統。

哇,好棒,想開始使用SLIME的slime-mode有哪些指令可以用,就看這裡吧。

參考文件:
1. Installing CLISP, Emacs, and SLIME on Windows XP from What’s In Peter’s Head。
2. Installing SBCL, Emacs, and SLIME on Windows XP from What’s In Peter’s Head。
3. SLIME User Manual from the offical website.
4. SLIME-HOWTO from Cliki

學習lisp :
1. Practical Common Lisp by Peter Seibel
2. The Common Lisp Cookbook
3. Structure and Interpretation of Computer Programs video lectures by Hal Abelson and Gerald Jay Sussman
4. Berkeley The Structure and Interpretation of Computer Programs by Satish B Rao and Brian HARVEY

GNU Emacs on Windows

問:這篇要講什麼?
答:要講如何在Windows XP上使用GNU Emacs,主要是給程序員看的。我的電腦是Windows XP。

問:為什麼?Emacs這個古董不是很難用嗎?
答:雖然常常聽說Emacs很難學難用,卻是功能最強大的編輯器,也是程序員終極的工具,所以我就想說用用看囉。

這是GNU Emacs的logo。


問:你欠揍嗎?難用還用?
答:或許吧,別打我臉啊~不~哪裡都別打~。

問:Emacs到底是什麼東西?
答:很難解釋,讓我試試看。Emacs是個功能超強大的編輯器,但這不是重點,重點是它的擴充延展性超強,任何人都可以在上面“開發”,所以有人說Emacs是一個完整的開發平台,可以跟火狐Firefox對比一下,任何人都可以在上面開發各種功能的外掛;一般普通的編輯器(或是其他軟體),如果沒有某項功能,你就無計可施,如果某項操作行為不合你意,你也莫可奈何,如果是功能強一點的編輯器,會提供很多設定參數讓你修改,譬如修改各功能對應的快捷鍵;但在Emacs上,遇到這樣的情況,你都可以自己動手寫程式加功能進去,或者修改原有的操作行為,譬如說寫程式時,關鍵字、變數函數名稱、字串、註解等等你希望用不同顏色顯示,你就可以自己寫出這樣的功能;其實既然可以在其上寫程式,那你想要什麼功能都是可以辦得到的,譬如執行shell、讀寫email、呼叫外部程式(例如compiler、spell checker)、科學計算機、甚至瀏覽器都可以。

這種可以讓使用者自己“擴展、延伸”原有軟體功能的特性,在其他軟體也可以看得見,Firefox的外掛就不用說了;還有Microsoft Excel,你可以因特殊需要來寫數學運算式;很多編輯器也都可以錄製巨集,把一連串的指令錄下來,重複使用節省操作時間。

累了嗎?打打打俄羅斯方塊吧。

在Emacs裡面顯示圖檔。

當然別忘了寫程式喔,詳細看看這張圖,其中有寫程式的、有列出目錄檔案的、有執行shell的、有列出函式清單等等,甚至可以執行版本控管指令(cvs, svn...)。

問:可是我不想開發那些功能啊?
答:Emacs已經有三十多年的歷史了,很多功能都已經開發出來了,一般來說你不必擔心,只要找一找,應該都有你想要的外掛功能。想想看Firefox上面豐富的外掛吧。

哇,編輯LaTex耶,好棒。

問:哦,那如果沒有呢?
答:那就要用Emacs的開發語言"Emacs Lisp(elisp, Elisp)"來撰寫,elisp是Lisp程式語言的一個分支,時至今日其主要用途就是在Emacs上開發各種功能,事實上,Emacs的核心部分是以C撰寫,其他大部分都是以elisp撰寫;所以如果沒有你要的功能,也沒有別人要開發的話,請你自己寫吧。本篇不介紹elisp。

問:聽起來很不錯,但是,看圖、打遊戲、讀email,我只要另外執行一支軟體不就得了,為什麼一定要在Emacs內執行?還有,你說的很多編輯功能,我的編輯器也都有啊?
答:是的沒錯,但我上面說的重點是:Emacs是個強大完整的開發平台,發現不足時,就會有人開發各種外掛,這是某些編輯器沒辦法做到的。

而上面強調說“你可以在Emacs裡面做任何事!”,你可能看不出有什麼好處,讓我解釋一下其中的歷史背景,在很久很久以前,沒有『視窗介面』的那個時代,你執行編輯器寫程式,寫一寫離開,執行compiler,在shell下執行程式看看結果,然後再回去編輯器修改,累了就執行email軟體,或是西洋棋,這樣跳來跳去很麻煩,因為整個畫面會被你的軟體佔據,不像現在一個軟體有自己的一個視窗,所以自然會產生希望“一支軟體通吃”這樣的想法;雖然後來這種文字命令列模式也發展出很多機制(譬如同時使用很多個終端機畫面,還有screen這隻程式讓你在多個軟體間跳來跳去)來因應,但對於習慣視窗圖形化操作介面與滑鼠的人來說,那些機制都不夠便利,所以“你可以在Emacs裡面做任何事!”對於以前在命令列下工作的人來說是一大福音,但現在我們在各種環境(Windows, Mac, Linux...)都有圖形化操作介面了,所以這點已經不是那麼有吸引力了。

不過再強調一下,因為Emacs是個強大完整的開發平台,如果你願意學習elisp,那你可以把Emacs調整成你最喜歡的樣子,如果你不願意,也可以找到一堆別人開發出來好用的外掛。


想想看以前沒有滑鼠的時代(嗯,好像很難想像),Emacs是那種時代的產物,所有的動作都要用鍵盤完成,想想看,你要把一整塊文字做搬移的動作,沒有滑 鼠游標可用,只有鍵盤要怎麼達成,很難吧;而且,Emacs還有個中心思想:讓雙手保持在打字區,利用各種按鍵組合完成任務,減少把手移動到鍵盤右邊(上 下左右方向鍵、編輯鍵Delete、Page Up、Home)的機會,所以要記憶很多按鍵組合。結論就是,對於習慣滑鼠操作的人,Emacs以鍵盤為主的操作方法很古老很難學。

有了視窗介面與滑鼠,就不需要記憶一堆指令與參數;現代人用電腦時大部分時間都只需要瀏覽器與滑鼠。

問:好吧,那麼Emacs都有三十多年的歷史了,說些故事來聽聽吧?
答:這就免了吧,可以拜拜孤狗大神,或是查查維基百科吧。

問:難道都沒有其他同等的好產品嗎?
答:有的,但每個人都有不同的看法。有人不管什麼程式語言都用UltraEdit,我以前在寫C/C++時,用的是Source Insight,但我很多同事都是用SlickEdit,當我在寫簡單的Java程式時,用的是TextPad,但現在開發Java的首選Eclipse,另外還聽說TextMate(Mac)與E Text Editor(Windows)很棒(聽說其外掛能力也很強大,但我只有些許的使用經驗),我最近寫簡單的程式時,都是用NotePad++,寫大程式是用Visual Studio(Windows)與Xcode(Mac),在unix上我還用過joe與pico,但資深的都用vi或emacs;如果你問其他人,這份編輯器清單會長到不像話,編輯器之優劣爭論似乎跟程式語言優劣爭辯一樣,永無止息之日,所以就此打住。

這張圖是Source Insight,是我第一個接觸到功能夠強大的編輯器,改用Source Insight後就再也不可能回到Visual C++ 6.0的編輯器了。

問:那你用Emacs是為了什麼?
答:雖然理想上我們希望一支編輯器走遍天下,但現實上,不同的情況會有不同的最佳選擇。而我用Emacs主要是為了學寫lisp程式(但非elisp而是common lisp與scheme),而寫lisp程式最好的開發環境似乎就是Emacs。所以我會另外寫一篇關於在Windows上安裝common lisp系統,以及SLIME(輔助開發的Emacs外掛)。

問:除了Emacs就沒有其他選擇來寫lisp程式嗎?
答:當然有,Lispbox、vim+limp、LispIDE、Jabberwocky 、Cusp on Eclipse。有空就自己試試看囉。

問:我不需要寫lisp,我的編輯器也夠強,我不想學Emacs,再見。
答:不送了。

問:好吧,我想用看看,聽說Emacs有很多版本?
答:Emacs有很多版本分支,例如GNU Emacs、XEmacs、Aquamacs等等,這篇用的是GNU Emacs on Windows。另外在Cygwin上也有GNU Emacs,但我用了覺得怪怪的,譬如一開始居然沒辦法關掉程式離開。

問:去哪下載?
答:在官方網站的下載區,可以下載原始碼,不過編譯好的Windows版已經準備好了(不支援95與NT之前的版本)。目前(寫這篇的時候)最新版本是23.2,進去後可以看到兩種檔案,一種是emacs-23.2-barebin-i386.zip,裡面只有編譯過後的執行檔,沒有elisp code以及一些文件,不是我們要的;另一種是emacs-23.2-bin-i386.zip(43 MB),這才是完整的。

問:怎麼安裝?
答:安裝只需把檔案解壓縮即可,但解壓縮後的目錄名最好不要有空格或太長,避免發生一些奇怪的問題。因為Emacs以及相關軟體都有很深的unix背景,為避免麻煩,我採用無空格的目錄名。還有,為了配合unix的一些習慣,我在C:\下新增了一些目錄:
C:\home
C:\home\bin
C:\home\bin\emacs (Emacs放這裡,我把解壓縮後的目錄名emacs-23.2改成emacs。)
C:\home\yehnan (這是我的使用者目錄)

PS 因為我之後還要安裝lisp systems,所以才會有這些目錄,如果你不需要,只要把Emacs解壓縮到任何一個地方即可。

然後執行C:\home\bin\emacs\runemacs.exe,哇,出現了。自己拉個捷徑到桌面吧。注意,不是emacs.exe,其實也可以,但會多出一個沒用的命令列視窗。
沒錯,不用安裝,Emacs也不會在其他地方產生奇奇怪怪的檔案,你甚至可以把Emacs放進隨身碟帶著走,沒有問題。

如果你想要文字模式的話,在命令列模式下輸入c:\home\bin\emacs\bin\emacs -nw即可。

問:我改變心意了,怎麼解除安裝?
答:把剛剛那些目錄通通砍掉,還有你建立的捷徑,這樣就好了。

問:接下來呢?怎麼用?
答:嗯,你可以看看Emacs Tutorial(Emacs快速指南),就在Emacs的開始畫面上;或是GNU Emacs Manual(官方手冊),可以線上閱讀或是下載;或是看本書Learning GNU Emacs 3/E,這本書我只有大概翻過,覺得還不錯。

問:這些文件怎麼看起來怪怪的?
答:我覺得Emacs官方寫的文件都很奇怪很囉嗦,因為:

第一,寫的人把用的人都當做初學者,沒有使用編輯器的經驗、甚至沒有使用電腦的經驗。看看這段話:
"Files are named units of text which are stored by the operating system for you to retrieve later by name.",拜託,我還需要你教我“檔案”是什麼東西嗎?

第二,寫文件的人似乎停留在1980年代,裡面很多術語都很老舊了,看看這個:
" We use the term frame to mean the entire terminal screen or graphical window used by Emacs."、"The main area of the frame, below the tool bar (if one exists) and above the echo area, is called the window.",哇哩咧,你的frame是我的window,你的window又是什麼鬼?

第三,寫文件的人還在緬懷以前的時光,有些在以前算特殊的功能,已經不再特殊了,例如:
"You are reading about GNU Emacs, the GNU incarnation of the advanced, self-
documenting, customizable, extensible editor Emacs.",啥?self-documenting?哪個編輯器沒有說明文件啊?extensible, customizable?現在很多編輯器多多少少都可以客製化了。

第四,有些功能太過強大,所以解釋起來很麻煩:
"You can yank text from the kill ring into any position in a buffer, including a position in a different buffer; the kill ring is shared by all buffers.",yank&kill在這裡等於cut&paste,而kill ring呢?大概是指你cut好幾次後,不會只剩下最後一次cut的東西,之前的都還在kill ring裡面。

第五,因為Emacs以鍵盤為主,雖然現在有滑鼠可以用了,可是其中心思想還是要求所有的功能動作都要能用鍵盤達到,以至於像“把一段文字圈選”這種以滑鼠可以輕易達到的功能,手冊要用好多篇幅講解鍵盤的指令。譬如說:
"Setting the mark at a position in the text also activates it. When the mark is active, Emacs indicates the extent of the region by highlighting the text within it, using the region face. After certain non-motion commands, including any command that changes the text in the buffer, Emacs automatically deactivates the mark; this turns off the highlighting. ",什麼鬼啊,滑鼠點一點拉一拉就好了啦。

傳說中,能學會Emacs眾多指令的人,可以得到美女的青睞。真的還假的啊?

我會建議你找找網路上的入門介紹,比較快上手,要詳細一點的話Learning GNU Emacs應該是本不錯的書,至於我嘛,我把GNU Emacs Manual當做參考手冊,不會一頁一頁看,而是碰到問題再去裡面找答案。而我接下來要做的就是幫你入門,介紹一些基本觀念。

注意:老實說我不熟悉Emacs,我只是將一些基本的東西寫下來,能夠用就好,慢慢地再去學高階一點的功能與用法。我使用Emacs最主要的目的是要寫lisp程式,並不打算將Emacs當做我唯一的編輯器。

問:首先講解一下畫面上的各個區域吧?
答:
首先,你看到的整個畫面,稱之為frame(一般的術語會稱之為window視窗),
然後在上面有一條Menu Bar(選單列,File Edit Options Buffers Tools Help),
然後是一些常用功能的Tool Bar(工具列),
最下面那一條叫做echo area(目前顯示著"For more information about GNU Emacs...");
在Tool Bar下面、echo area上面的整個區域稱之為window,是用來顯示編輯內容,window還包括灰色的那條Mode Line。

重點:Emacs把整個畫面叫做frame,把編輯一個檔案的地方叫做window。



問:好,接下來,怎麼新增檔案、開啟舊檔?
答:
方法一:用滑鼠把檔案從檔案總管拖曳進Emacs的畫面。
方法二:使用File->Visit New File...,新舊檔都可以。
方法二:使用File->Open File...,開舊檔。

稍微介紹一下“visit”(Emacs術語),意思是,產生出一個新的buffer,把檔案內容拷貝進去,然後把buffer內容秀在window上。接下來你就可以編輯了。

重點:所謂buffer就是一塊記憶體,Emacs把放檔案內容放進去讓我們編輯。


這裡要解說一下那條灰色的Mode Line。
最左邊的“-”,無意義。
左邊數來第二欄,代表編碼系統(Coding System)。如果是“-”,大概代表“undecided”或是“us-ascii”,表示英文,如果是“B”,代表“chinese-big5(cp950)”,表示中文,這是中文版Windows預設的編碼,如果是“U”,表示unicode,可以把滑鼠移到上面,會出現一些資訊;通常我們都不用去管,Emacs都可以正確判斷與處理。
左邊數來第三欄,代表End-Of-Line。如果是“\ or DOS”表示CRLF;“/ or Mac”表示CR;“: or Unix”表示LF。如果忘記了,就把滑鼠停在上面,會顯示資訊給你看。通常我們也不用管這個。
左邊數來第四欄跟第五欄,第四欄表示buffer是否唯讀,第五欄表示buffer是否有被修改(跟檔案內容不一樣了),“--”表示buffer未修改,“**”表示buffer有修改,“%*”表示buffer雖是唯讀但有修改,“%%”表示其他狀態。
左邊數來第六欄,通常是“-”,滑鼠停在上面會顯示檔案路徑,如果路徑是在遠方機器上,會顯示“@”。

好吧,你是不是頭昏眼花了,其實上面這些,通常我們都不用管,而且你把滑鼠移上去就知道了,不需硬記。

然後是buffer名稱,通常跟檔案名稱一樣。但有時候你會看到*Message*、*scratch* 、*GNU Emacs*、*Help*等等,這些有星號的buffer,都是Emacs內部會用到的,不用管他也不用急著把他們關掉。譬如你在echo area會看到很多訊息,這些訊息也會寫入到*Message*內。

然後是位置資訊,可能是“All or Top or Bot or nn%”,看就知道了。
然後是游標在第幾行,Lxx。

然後是所謂的Major Mode跟一些附加資訊(如果有的話),所謂major mode就是根據你在編輯的文件種類的不同,設計的編輯模式,例如“Text”、“HTML”、“Fundamental”、“C”等等。

然後是一連串的“---”,把滑鼠移上去會發現可以對window調整大小、放到最大、或移除掉;自己試試看就清楚了。

問:怎麼儲存檔案、另存新檔?
答:請問File->Save或是File->Save As...。

問:開了很多檔,到底有多少buffers啊?怎麼切換?
答:到選單Buffers就可以看到全部的buffers,點選就會切換。

問:怎麼把frame分割成多個windows用來分別顯示不同的buffers?
答:File->Split Window,可以上下分割。至於要左右分割的話,Emacs沒有把這個命令放到選單內,所以,我們終於要輸入Emacs指令囉,

左右分割的指令是“C-x 3”,C代表Ctrl,C-x代表按住Ctrl不放的同時按下x,然後放開,再按3。

任何時候,如果發現輸入指令有錯,可按C-g取消,或是按兩三次的ESC。

問:怎麼把window移除?
答:在mode line上按滑鼠右鍵即可。雖然把window移除,但buffer還是存在的。

問:那怎麼把一個buffer關閉?
答:File->Close。

問:我打錯東西要按C-z取消,怎麼反而把Emacs縮到最小了?
答:想要做undo(一般軟體都以C-z表示),Emacs的指令是C-/。

問:怎麼C-c、C-x、C-v不是複製剪下貼上啊?
答:可以到Options->C-x/C-c/C-v Cut and Paste (CUA),打開這個模式即可。

問:怎麼搜尋?
答:C-s往下尋找,C-r往上尋找。

問:那怎麼把Emacs關掉?
答:按整個畫面右上角的叉叉,或是File->Quit,如果有未存檔的buffer,會詢問你。

問:那接下來?
答:接下來就靠你自己了,我不是Emacs高手,只是因想寫lisp程式需要用Emacs,所以把初步介紹寫下來,下面有一些參考文件,可以作為你的下一步。

聽說Emacs很難學是不是真的啊?不是,正確說法應該是:很詭異。

參考資料:
1. GNU Emacs Manual
2. GNU Emacs FAQ For MS Windows
3. Xah's Emacs Tutorial
3. 炎龍的筆記本
4. Emacs 初學者