2006年10月9日
Currently the concept of Progressive Enhancement is getting hotter and hotter. It emphasizes accessibility, semantic markup, and the importance of separating the complex rich interaction logic into well modularized javascript files. It's really a usefully way of thinking about how to modularize and manage web presentation components. But the Rails framework doesn't have good support for PE, so we have to define our own convention and helpers to make our life easier.
Usually, I'd like to organize js files in the Rails convention, which means we'll have something like this:
app
|
- views
|
- admin
|
_ new.html.erb
- index.html.erb
public
|
- javascripts
|
- admin
|
- new.js
- index.js
And new.js looks similar to:
$(document).ready(function() {
enhanceInteractionOnElements();

});
function helper_methods() {

}
Then, add the follow method to ApplicationHelper module:
def page_javascript_include_tag
file = "#{params[:controller]}/#{params[:action]}.js"
File.exist?("#{RAILS_ROOT}/public/javascripts/#{file}") ? javascript_include_tag(file) : ""
end
this method will look for js file for a particular page. And in you layout file, add one line in the head sectin:
<%= page_javascript_include_tag %>
That's it. Whenever you request an action of a particular controller, it will find and include the PE js files automatically. Now we've very very primitive support of PE in Rails framework now.
前幾天在JavaEye海闊被標題黨陰了一把,看了一篇轉的文章叫《 被中國人誤傳了數千年的七句話》,頗有些哭笑不得的感慨:
1. 這些話的確是被誤傳了不假,但是最多也就一百年吧。中國知識分子不讀四書五經史子集的壞風氣大抵是開始于所謂的新文化運動吧。再往前的人,對于這些典籍字字爬梳,提了上句馬上背下句,就算是以章句式解讀為主的宋元,也不應該隨隨便便就被忽悠了,更不用說反對宋儒理學講究正本清源的明清了。
2. 古人斷章取義是一種風雅的言談習慣,所謂“雅言”是要字字出典的,有點像對暗號。比如我們家貓跑了,擱古代我肯定問“誰之過歟?”,十有八九會回答說,“言是典守者之過也”,這句射的是“虎兕出于柙”,正好應景。甚至為了詼諧應景,故意曲解文義的情況也是很常見的。如果以此為證說誤傳的話,恐怕只能算是牛嚼牡丹了。順便多說一句,其實這個毛病現代人也有,不過不再是古文了,大多數是電影電視臺詞:“空氣在顫抖仿佛天空在燃燒。是啊,暴風雨就要來了”,“道哥,牌子啊”,“你看我的英語,有沒有長進”之類的,雖不復古韻,但也還算有趣。
P.S. : 今天team里有人把David Wheeler的名言,貼在了Quote Wall上:“Any problem in computer science can be solved with another layer of indirection.”
這到的確算是一句被誤傳的名言吧,原文是“Any problem in computer science can be solved with another layer of indirection. But that usually will create another problem.”
Aurum is a Ruby-based LALR(n) parser generator that you can use to develop your own domain specified languages, scripting languages and programming languages.Although it's just yet another parser generator, Aurum is slightly different from other widely used parser generators:
- One of major targets of Aurum is to simplify external DSL development, espectually Ruby external DSL.
- Aurum uses incremental LALR(n) algorithm instead of the common used LALR(1)/Full LALR(n) algorithm. That means:
- Allowing the user to express grammars in a more intuitive mannar.
- Making it easier to handle complicated grammars. For exmaple,
COBOL(LALR(2 or 3)), simplified nature language(LALR(3+)) and etc.
- Closer to Generalized LR in language recognizing but much more faster.
- Smaller parsing table comparing to Full LALR/LR(n) algorithm.
- Aurum supports grammar reuse, and itslef'll be shipped with some pre-defined common structures. One of the pain points of external DSL is that you have to re-define lots of common structures, such as if statements, block structure and etc. With Aurum, you could simply reuse them.
- Aurum uses a Ruby interal DSL as meta-language, and provides a generic lexer/parser as well. You could test your grammar by the comprehensive testing libraries Ruby has(you could even develop your lexer/parser in the TDD fashion).
- As the name suggested, Aurum, the Latin word for Gold, is partially inspired by the GOLD Parsing System. The grammar you created with Aurum could be completely independent of any implementation language,even Ruby.(not implemented yet :) )
Ok, let's start from the 'Hello World in Compiler Construction' —— Expression Evaluation
1 require 'aurum'
2
3 class ExpressionGrammar < Aurum::Grammar
4 tokens do
5 ignore string(' ').one_or_more # <= a
6 _number range(?0, ?9).one_or_more # <= b
7 end
8
9 precedences do # <= c
10 left '*', '/'
11 left '+', '-'
12 end
13
14 productions do # <= d
15 expression expression, '+', expression {expression.value = expression1.value + expression2.value} # <= e
16 expression expression, '-', expression {expression.value = expression1.value - expression2.value}
17 expression expression, '*', expression {expression.value = expression1.value * expression2.value}
18 expression expression, '/', expression {expression.value = expression1.value / expression2.value}
19 expression '(', expression, ')' do expression.value = expression1.value end # <= f
20 expression _number {expression.value = _number.value.to_i}
21 expression '+', _number {expression.value = _number.value.to_i}
22 expression '-', _number {expression.value = -_number.value.to_i}
23 end
24 end
If you has any experience with other compiler compiler/parser generator, you probably could understand what happens above quite easily. Instead of explaining things like token, character class, and production, I'd like to emphasise some Aurum conventions:
- At point a, we use 'ignore' directive to declare the ignored pattern, such as whitespaces etc.'string' is one of the helper methods(others are enum, range and concat), which is used to define lexical patterns. It will create a pattern matching the given string exactly.
- At point b, we declare a lexical token named '_number'. In Aurum, lexical tokens, or terminals from syntax perspective, always start with '_'. The expression '_token_name pattern' is equivalent to 'match pattern, :recognized => :_toke_name'. The 'match' directive is a common way to associate lexical action with leixcal pattern.
- At point c, we declare operator precedences of the Expression grammar.The eariler the operators definied, the higher precedence they will have.
- At point d, we declare syntax rules of Expression grammar. According to Aurum naming convention, all terminals should start with '_' while all nontermainls start with lower case alphabet character. String literals will be interpreted as reserve words, and added to lexer automatically.
- At point e, we define a semantic action to the Addition rule. In semantic action, you could access to the objects in value stack via the name of corresponding symbols.If there are more than one symbol with the same name, you could differentiate them by the order they appered in the production.
- At point f, we use do..end instead of {..}. Using Ruby internal DSL as meta-langauge is a double-side sword, you have to bear its flaws while enjoying the remaining parts. There is no perfect world, isn't it?
Now, let's find out how we could use this expression grammar. You could use the helper method as below(it will recalcuate lexical table and parsing table for every call, could be quite slow):
1 puts ExpressionGrammar.parse_expression('1+1').value
or use the lexical table and parsing table to create your own lexer & parser:
1 lexer = Aurum::Engine::Lexer.new(ExpressionGrammar.lexical_table, '1+1')
2 parser = Aurum::Engine::Parser.new(ExpressionGrammar.parsing_table(:expression))
3 puts parser.parse(lexer).value
At the end of this post, I'd like to give another grammar example coming from Martin Fowler's HelloParserGenerator series:
1 require 'aurum'
2
3 Item = Struct.new(:name)
4
5 class Catalog < Aurum::Grammar
6 tokens do
7 ignore enum(" \r\n").one_or_more
8 _item range(?a,?z).one_or_more
9 end
10
11 productions do
12 configuration configuration, item {configuration.value = configuration1.value.merge({item.value.name => item.value})}
13 configuration _ {configuration.value = {}}
14 item 'item', _item {item.value = Item.new(_item.value)}
15 end
16 end
17
18 config = Catalog.parse_configuration(<<EndOfDSL).value
19 item camera
20 item laser
21 EndOfDSL
22
23 puts config['camera'].name
P.S.:The post is based on the developing version of Aurum(0.2.0). You could get it from the svn repository.
P.S.P.S.: There is a more complicated example in the examples directory, a simple Smalltalk interpreter. Have fun:)
A very brief introduction to Aurum
Aurum是一個用Ruby實現的LALR(n) parser generator(是的,又是一個parser generator),不過它和其他一些廣泛應用的parser generator相比略有不同的:
1.Aurum的主要目標之一,是簡化external DSL的開發(尤其是ruby external DSL)。
2.Aurum采用增量LALR(n)算法,而不是通常的LALR(1)。這意味著:
a.不必由于LALR(1)能力的限制,而改寫語法,很多在LALR(1)中沖突的語法在LALR(n)中可以比較自然地表達。
b.由于識別能力的增強,可以處理一些比較復雜的語法,比如COBOL(LALR(2)或LALR(3)),比如一些簡化的自然語言(LALR(3+))。
c.處理能力接近Generalized LR,卻快很多
d.比起Full LALR/LR(n),增量算法生成的語法表更小。
3.出于簡化external DSL實現的考慮,Aurum支持語法重用。
4.Aurum采用Ruby internal DSL作為語法聲明的元語言,可以利用Ruby豐富的測試框架,有效地對編譯/解釋/分析器進行測試。
5.正如名字所暗示的,Aurum(Gold的化學名稱)的一部分靈感來自 GOLD parsing system,它將支持獨立于平臺和語言的編譯器開發。
好,閑話少說,看一個例子,編譯原理中的Hello World —— 表達式求值:
1 require 'aurum'
2
3 class ExpressionGrammar < Aurum::Grammar
4 tokens do
5 ignore string(' ').one_or_more # <= a
6 _number range(?0, ?9).one_or_more # <= b
7 end
8
9 precedences do # <= c
10 left '*', '/'
11 left '+', '-'
12 end
13
14 productions do # <= d
15 expression expression, '+', expression {expression.value = expression1.value + expression2.value} # <= e
16 expression expression, '-', expression {expression.value = expression1.value - expression2.value}
17 expression expression, '*', expression {expression.value = expression1.value * expression2.value}
18 expression expression, '/', expression {expression.value = expression1.value / expression2.value}
19 expression '(', expression, ')' do expression.value = expression1.value end # <= f
20 expression _number {expression.value = _number.value.to_i}
21 expression '+', _number {expression.value = _number.value.to_i}
22 expression '-', _number {expression.value = -_number.value.to_i}
23 end
24 end
如果諸位對之前有用過compiler compiler或者parser generator的話,應該能看個七七八八吧。我大概解釋一下:
a.這里定義了文法空白,也就是被lexer忽略的部分,在通常的語言中,是空格回車換行之類的字符;string是用于定義lexical pattern的helper方法(出了string之外,還有range, enum和concat);ignore是一個預定義的說明指令,表示若文本匹配給定模式則該文本會被lexer自動忽略,其格式為:
ignore pattern {//lexical action}
b.此處為lexical token聲明,所有lexical token必須以_開頭,其格式為:
_token_name pattern {//lexical action}
這里其實是一個簡略寫法,等價于
match pattern, :recognize => :_token_name
c.此處為運算符優先級聲明,支持左/右結合運算符(無結合屬性運算符開發中);每一行中所有運算符具有相同優先級;比它下一行的運算符高一個優先級。比如在這個例子中,'*'和'/'具有相同優先級,但是比'+'和'-'的優先級別高。
d.此處為語法規則聲明,所使用的symbol主要有三種,nonterminal(小寫字母開頭),terminal(其實就是lexical token,以_開頭)和literal(字符串常量),其中所有literal都會被自動聲明為保留字。
e.此處定義了一條文法規則(加法),以及對應的semantic action。在semantic action中可以直接通過symbol的名字來獲取值棧中的對象。如遇到同名symbol,則按照出現順序進行編號即可。
f.其實這個沒啥,只不過由于我們使用的是Ruby DSL,所以有時候不能都用{},需要do end,這就是一個例子。
最后測試一下實際中如何使用定義好的語法(使用helper method,注意由于分析表沒有緩存,每次都會重算語法表,僅僅適用于debug mode。)
puts ExpressionGrammar.parse_expression('1+1').value
或者通過分析表自己構造lexer和parser
lexer = Aurum::Engine::Lexer.new(ExpressionGrammar.lexical_table, '1+1')
parser = Aurum::Engine::Parser.new(ExpressionGrammar.parsing_table(:expression))
puts parser.parse(lexer).value
最后最后,給另外一個例子,就是 Martin Fowler Blog上的 HelloParserGenerator系列中所用的語法:
1 require 'aurum'
2
3 Item = Struct.new(:name)
4
5 class Catalog < Aurum::Grammar
6 tokens do
7 ignore enum(" \r\n").one_or_more
8 _item range(?a,?z).one_or_more
9 end
10
11 productions do
12 configuration configuration, item {configuration.value = configuration1.value.merge({item.value.name => item.value})}
13 configuration _ {configuration.value = {}}
14 item 'item', _item {item.value = Item.new(_item.value)}
15 end
16 end
17
18 config = Catalog.parse_configuration(<<EndOfDSL).value
19 item camera
20 item laser
21 EndOfDSL
22
23 puts config['camera'].name
P.S.:本文是根據Aurum0.2.0寫成的,你可以從rubyforge的svn上得到它。
P.S.P.S.: 在exmaples目錄里有一個更復雜一些的例子,是一個簡單的Smalltalk解釋器。
《Programming Erlang》第8章后面有一個練習,Ring Benchmark。就是說創建N個進程,把它們組合成環狀。然后在這個環上把一條消息在環上傳遞M圈,然后記錄所有的時間。實現起來也挺簡單,20行左右吧:
1 -module(ring_benchmark). 2 -export([start/2]). 3 4 start(N, M) -> 5 Pid = create_process(self(), N - 1, M), 6 time(fun() -> Pid ! start, loop(Pid, M) end). 7 8 time(Fun) -> 9 statistics(wall_clock), 10 Fun(), 11 {_,Time} = statistics(wall_clock), 12 io:format("Run : ~w s ~n", [Time/1000]). 13 14 create_process(Pid, 0, _) -> Pid; 15 create_process(Pid, N, M) -> create_process(spawn(fun() -> loop(Pid, M) end), N - 1, M). 16 17 loop(_, 0) -> void; 18 loop(Next, M) -> 19 receive 20 Message -> Next ! Message, 21 loop(Next, M - 1) 22 end. 23 24
有意思是它還有一個第二問,讓你用另外一種熟悉的語言實現同樣的功能,發送同樣多的消息,也把時間記錄下來,然后寫一篇blog來publish你的結果。其實,大家心知肚明,這種lightweight process啊,message passing concurrency啊都是Erlang的強項,而且實測結果也著實頗為恐怖,一般也就沒那閑心拿別的東西來陪襯一把了(Armstrong同學自己實現了一個Java version,效率大約能差到百倍吧)。不過還真有那寫不信邪的老大, 用stackless python實現了同樣的ring benchmark,發現比erlang還快...后來修改代碼去掉io操作,Erlang倒是比stackless python快些,但也只是一些而已。
http://www.dcs.ed.ac.uk/home/stg/fengshui.ps.gz今天早上打開Google Reader就看見這么一篇,內容倒也罷了,不過是bad smell的另一個名字而已,硬要扯上分水也只能算是勉勉強強。不過郁悶的是,竟然是個洋人的手筆,國學不昌實不能不令我輩心憂啊。 p.s. 預計未來6個月口頭禪:"你這寫當心壞了項目的風水"
http://www.infoq.com/cn/articles/domain-web-testing 應用Selenium進行Web測試往往會存在幾個bad smell: 1.大量使用name, id, xpath等頁面元素。無論是功能修改、UI重構還是交互性改進都會影響到這些元素,這使得Selenium測試變得非常脆弱。 2.過于細節的頁面操作不容易體現出行為的意圖,一段時間之后就很難真正把握測試原有的目的了,這使得Selenium測試變得難于維護。 3.對具體數據取值的存在依賴,當個別數據不再合法的時候,測試就會失敗,但這樣的失敗并不能標識功能的缺失,這使得Selenium測試變得脆弱且難以維護。 而這幾點直接衍生的結果就是不斷地添加新的測試,而極少地去重構、利用原有測試。其實這到也是正常,單元測試測試寫多了,也有會有這樣的問題。不過比較要命的是,Selenium的執行速度比較慢(相對單元測試),隨著測試逐漸的增多,運行時間會逐漸增加到不可忍受的程度。一組意圖不明難以維護的Selenium測試,可以很輕松地在每次build的時候殺掉40分鐘甚至2個小時的時間,在下就有花2個小時坐在電腦前面等待450個Selenium測試運行通過的悲慘經歷。因此合理有效地規劃Selenium測試就顯得格外的迫切和重要了。而目前比較行之有效的辦法,往大了說,可以叫domain based web testing,具體來講,就是Page Object Pattern。 Page Object Pattern里有四個基本概念:Driver, Page, Navigator和Shortcut。Driver是測試真正的實現機制,比如Selenium,比如Watir,比如HttpUnit。它們懂得如何去真正執行一個web行為,通常包含像click,select,type這樣的表示具體行為的方法;Page是對一個具體頁面的封裝,它們了解頁面的結構,知道諸如id, name, class,xpath這類實現細節,并描述用戶可以在其上進行何種操作;Navigator則代表了URL,表示一些不經頁面操作的直接跳轉;最后Shortcut就是helper方法了,需要看具體的需要了。下面來看一個超級簡單的例子——測試登錄頁面。 1. Page Object 假設我們使用一個單獨的Login Page進行登錄,那么我們可能會將登錄的操作封裝在一個名為LoginPage的page object里:
1 class LoginPage 2 def initialize driver 3 @driver = driver 4 end 5 6 def login_as user 7 @driver.type 'id= ', user[:name] 8 @driver.type 'xpath= ', user[:password] 9 @driver.click 'name= ' 10 @driver.wait_for_page_to_load 11 end 12 end
login_as是一個具有業務含義的頁面行為。在login_as方法中,page object負責通過依靠id,xpath,name等信息完成登錄操作。在測試中,我們可以這樣來使用這個page object:
1 page = LoginPage.new $selenium 2 page.login_as :name => 'xxx', :password => 'xxx' 3
不過既然用了ruby,總要用一些ruby sugar吧,我們定義一個on方法來表達頁面操作的環境:
1 def on page_type, &block 2 page = page_type.new $selenium 3 page.instance_eval &block if block_given? 4 end
之后我們就可以使用page object的類名常量和block描述在某個特定頁面上操作了:
1 on LoginPage do 2 login_as :name => 'xxx', :password => 'xxx' 3 end 4
除了行為方法之外,我們還需要在page object上定義一些獲取頁面信息的方法,比如獲取登錄頁面的歡迎詞的方法:
def welcome_message @driver.get_text 'xpath= ' end
這樣測試也可表達得更生動一些:
1 on LoginPage do 2 assert_equal 'Welcome!', welcome_message 3 login_as :name => 'xxx', :password => 'xxx' 4 end
當你把所有的頁面都用Page Object封裝了之后,就有效地分離了測試和頁面結構的耦合。在測試中,只需使用諸如login_as, add_product_to_cart這樣的業務行為,而不必依靠像id,name這些具體且易變的頁面元素了。當這些頁面元素發生變化時,只需修改相應的page object就可以了,而原有測試基本不需要太大或太多的改動。 2. Assertation 只有行為還夠不成測試,我們還要判斷行為結果,并進行一些斷言。簡單回顧一下上面的例子,會發現還有一些很重要的問題沒有解決:我怎么判斷登錄成功了呢?我如何才能知道真的是處在登錄頁面了呢?如果我調用下面的代碼會怎樣呢?
1 $selenium.open url_of_any_page_but_not_login 2 on LoginPage { }
因此我們還需要向page object增加一些斷言性方法。至少,每個頁面都應該有一個方法用于判斷是否真正地達到了這個頁面,如果不處在這個頁面中的話,就不能進行任何的業務行為。下面修改LoginPage使之包含這樣一個方法:
1 LoginPage.class_eval do 2 include Test::Unit::Asseration 3 def visible? 4 @driver.is_text_present( ) && @driver.get_location ==  5 end 6 end
在visible?方法中,我們通過對一些特定的頁面元素(比如URL地址,特定的UI結構或元素)進行判斷,從而可以得之是否真正地處在某個頁面上。而我們目前表達測試的基本結構是由on方法來完成,我們也就順理成章地在on方法中增加一個斷言,來判斷是否真的處在某個頁面上,如果不處在這個頁面則不進行任何的業務操作:
1 def on page_type, &block 2 page = page_type.new $selenium 3 assert page.visible?, "not on #{page_type}" 4 page.instance_eval &block if block_given? 5 page 6 end 7
這個方法神秘地返回了page對象,這里是一個比較tricky的技巧。實際上,我們只想利用page != nil這個事實來斷言頁面的流轉,比如,下面的代碼描述登錄成功的頁面流轉過程:
on LoginPage do assert_equal 'Welcome!', welcome_message login_as :name => 'xxx', :password => 'xxx' end assert on WelcomeRegisteredUserPage
除了這個基本斷言之外,我們還可以定義一些業務相關的斷言,比如在購物車頁面里,我們可以定義一個判斷購物車是否為空的斷言:
1 def cart_empty? 2 @driver.get_text('xpath= ') == 'Shopping Cart(0)' 3 end
需要注意的是,雖然我們在page object里引入了Test::Unit::Asseration模塊,但是并沒有在斷言方法里使用任何assert*方法。這是因為,概念上來講page object并不是測試。使之包含一些真正的斷言,一則概念混亂,二則容易使page object變成針對某些場景的test helper,不利于以后測試的維護,因此我們往往傾向于將斷言方法實現為一個普通的返回值為boolean的方法。 3. Test Data 測試意圖的體現不僅僅是在行為的描述上,同樣還有測試數據,比如如下兩段代碼:
1 on LoginPage do 2 login_as :name => 'userA', :password => 'password' 3 end 4 assert on WelcomeRegisteredUserPage 5 6 registered_user = {:name => 'userA', :password => 'password'} 7 on LoginPage do 8 login_as registered_user 9 end 10 assert on WelcomeRegisteredUserPage
測試的是同一個東西,但是顯然第二個測試更好的體現了測試意圖:使用一個已注冊的用戶登錄,應該進入歡迎頁面。我們看這個測試的時候,往往不會關心用戶名啊密碼啊具體是什么,我們關心它們表達了怎樣的測試案例。我們可以通過DataFixture來實現這一點:
1 module DataFixture 2 USER_A = {:name => 'userA', :password => 'password'} 3 USER_B = {:name => 'userB', :password => 'password'} 4 5 def get_user identifier 6 case identifier 7 when :registered then return USER_A 8 when :not_registered then return USER_B 9 end 10 end 11 end
在這里,我們將測試案例和具體數據做了一個對應:userA是注冊過的用戶,而userB是沒注冊的用戶。當有一天,我們需要將登錄用戶名改為郵箱的時候,只需要修改DataFixture模塊就可以了,而不必修改相應的測試:
1 include DataFixtureDat 2 3 user = get_user :registered 4 on LoginPage do 5 login_as user 6 end 7 assert on WelcomeRegisteredUserPage
當然,在更復雜的測試中,DataFixture同樣可以使用真實的數據庫或是Rails Fixture來完成這樣的對應,但是總體的目的就是使測試和測試數據有效性的耦合分離:
1 def get_user identifier 2 case identifier 3 when :registered then return User.find ' .' 4 end 5 end
4.Navigator 與界面元素類似,URL也是一類易變且難以表達意圖的元素,因此我們可以使用Navigator使之與測試解耦。具體做法和Test Data相似,這里就不贅述了,下面是一個例子:
1 navigate_to detail_page_for @product 2 on ProductDetailPage do 3 . 4 end
5. Shortcut 前面我們已經有了一個很好的基礎,將Selenium測試與各種脆弱且意圖不明的元素分離開了,那么最后shortcut不過是在蛋糕上面最漂亮的奶油罷了——定義具有漂亮語法的helper:
1 def should_login_successfully user 2 on LoginPage do 3 assert_equal 'Welcome!', welcome_message 4 login_as user 5 end 6 assert on WelcomeRegisteredUserPage 7 end
然后是另外一個magic方法:
1 def given identifer 2 words = identifier.to_s.split '_' 3 eval "get_#{words.last} :#{words[0..-2].join '_'}" 4 end
之前的測試就可以被改寫為:
def test_should_xxxx should_login_successfully given :registered_user end
這是一種結論性的shortcut描述,我們還可以有更behaviour的寫法:
1 def login_on page_type 2 on page_type do 3 assert_equal 'Welcome!', welcome_message 4 login_as @user 5 end 6 end 7 8 def login_successfully 9 on WelcomeRegisteredUserPage 10 end 11 12 def given identifer 13 words = identifier.to_s.split '_' 14 eval "@#{words.last} = get_#{words.last} :#{words[0..-2].join '_'}" 15 end
最后,測試就會變成類似驗收條件的樣子:
1 def test_should_xxx 2 given :registered_user 3 login_on LoginPage 4 assert login_successfully 5 end
總之shortcut是一個無關好壞,只關乎想象力的東西,盡情揮灑Ruby DSL吧:D 結論 Selenium是一個讓人又愛又恨的東西,錯誤地使用Selenium會給整個敏捷團隊的開發節奏帶來災難性的影響。不過值得慶幸的是正確地使用Selenium的原則也是相當的簡單: 1.通過將脆弱易變的頁面元素和測試分離開,使得頁面的變化不會對測試產生太大的影響。 2.明確指定測試數據的意圖,不在測試用使用任何具體的數據。 3.盡一切可能,明確地表達出測試的意圖,使測試易于理解。 當然,除了遵循這幾個基本原則之外,使用page object或其他domain based web testing技術是個不錯的選擇。它們將會幫助你更容易地控制Selenium測試的規模,更好地平衡覆蓋率和執行效率,從而更加有效地交付高質量的Web項目。 鳴謝 此文中涉及的都是我最近三周以來對Selenium測試進行重構時所采用的真實技術。感謝Nick Drew幫助我清晰地劃分了Driver, Page, Nagivator和Shortcut的層次關系,它們構成我整個實踐的基石;感謝Chris Leishman,在和他pairing programming的過程中,他幫助我錘煉了Ruby DSL;還有Mark Ryall和Abhi,是他們第一次在項目中引入了Test Data Fixture,使得所有人的工作都變得簡單起來。
最近很多時間都在用Ruby,逐漸地發現了一件很不爽的事情,就是Ruby的end關鍵字。block多套幾層,很容易就最后一頁都是end了...難怪有人說,ruby不過是另一種acceptable Lisp,“最后一頁都是括號”的經典標志以另外一種形式復現了...對于Lisp的括號,我還是可以接受的,但是滿眼的end,直接讓我回憶起10年前沖刺NOI的種種,CPU直接切換到實模式,什么可讀啊小粒度方法全都沒有了,審美觀赤裸地變為短小精悍...最后殺紅了眼,一行算出文法定義的所有nullable symbols...
1 while @productions.inject(false) {|c, p| c |= !nullable?(p.nonterminal) && p.symbols.all? {|s| nullable? s} && @nullables << p.nonterminal}
注意1不是行號...這句用的statement modifier, 1是我能想到的最小ruby語句了... p.s. 我現在已經恢復到OO保護模式了...剛才追求短小過了頭的同時,發現了ruby bulid-in object的一個陷阱... a = Array.new 5, [] [[],[],[],[],[]] a[0] << 1 [[1],[1],[1],[1],[1]] 想不到華麗的Array直接假設傳進去的都是值對象了,好歹您也調個dup啊...
Which Programming Language are You?p.s. 這個可能不準...因為李默同學竟然是Lisp...怎么可能...
剛才和李默同學回憶了一下,發現我自從入行以來做了很多x項目...下面一一列舉一下。
1. IEC61970 Metadata: Electricity Power Trading System
當時剛上班,team里有一個Doamin知識很厲害的清華的博士,畢業的論文就是電力市場,而清華又是國家引入IEC61970的五家之一。所以他很超前的把這兩個東西結合在一起,做成了一個系統。說實話,剛了解IEC61970的時候,我是相當的震撼的,有趕上那時候MDA風氣剛起,IEC61970又是同時MOF(Meta Object Facility)和RDF based,華麗得不行。一下子我就變成了一個MDA guy,一個metadata guy...以至于,在BJUG最初的2年里,MDA/MOF/Metadata成為了主旋律...
2. IEC61970 & CWM(Common Warehouse Metamodel) & Office Plugin : Data Warehouse Integration System
這是迄今為止,我最不愿意回憶的一個項目...因為Office Plugin...動輒藍屏的遭遇讓我心有余悸...這是一個backend是J2EE,frontend是.Net的office插件系統,主要是報表...兩邊都使用CWM作為數據統一的形式...基本上做到一半我的意志就崩潰了...
3. DB Migration/Refactoring : Jyxpearl
這個項目...是李默同學的私房最愛,從大學一直做了很久,改版無數次...當時沒有這么流行的好詞,什么DB Migration啊,DB Refactoring啊,那時候我們統稱導數據...我導了好多會...基本上線一回導一回...時至今日...李默同學總是不無得意的說:你看,你DB Migration的能力就是我培養的...
4. JMI(Java Metadata Interface) & Eclipse RCP : Multi/Rich Client ERP Product
這個team其實挺華麗的,老欒的產品經理,李默是開發經理,超級資深行業專家(人家實際做過生產科長,MRPII,ERP都是人家玩剩下的)老齊做需求,俺是Architect,還有動物園里的豬Senior Dev,我認識人中美工能力第一交互設計能力第一的米米姐做UI和交互。由于當時看了netbeans和sun的官方JMI實現得太玩具。我們決定從自己的JMI實現開始,系統結構要求多客戶端,web,rcp都要...所以是超輕http協議的b/s,c/s。結構還是不錯的,過程李默和我當然是敏捷了。似乎一起都超級完美的時候,就是要壞菜的時候...企業事業部解散了...
5. Java Communication & Eclipse RCP : IC Card Reader
上面那個項目解散之后,我跟李默賦閑在家,有不忍心打擾政府,自謀生路找的項目...這個項目要用IC卡讀卡器,為了鍛煉我們的Eclipse RCP能力,我們決定用eclipse rcp來做。于是問題就出來了...IC卡怎么辦?google一把發現天無絕人之路...Java有一個Communication包,可以連接serial port...不過當時tricky的是...我的本子沒有串口,我們買了一個串口到usb的轉換器...發現根本不能用...于是只好跑到李默家用他華麗的臺式機(這廝當年誓言旦旦的說,laptop太慢,一定要用臺式機,東借西借搞了個2G RAM SATA[注意,這是伏筆]的機器)。我當時就覺得,Java的這個東西基本就是充數的,貌似完全沒有人用過,文檔啥的都特少...只能自己摸索。在經歷了無數次失敗之后,終于成功了。在showcase那天的上午,我最后實驗了讀卡什么的,都沒問題。興高采烈的把jar拷到優盤上,剛插到usb口上...只見一道閃電...機器黑了...據李默后來分析是主板燒了...我說沒事,拿上硬盤,土一點也不影響showcase。李默說...這個...SATA耶...還不流行呢...我綠...此后很長時間,我都懷疑是我跟李默同學范沖,超級項目殺手...
6. RDF, Semantic Web, SparQL : Ontology-Relationship DB Mapping
這是在一家公司做產品,當時我元數據/MDA領域頗有積累...跟這家公司做得類似,就過來負責研發本體到關系數據庫的映射...兼帶在D2RQ的基礎上實現一個SparQL查詢語言。怎么樣...聽上去很華麗吧...到現在我都認為,這個項目是我最有潛力的牛皮,不定那天web x.0了,我也老了,我就可以拉著小朋友的手去吹牛b了"05年我就做semantic web,O/R mapping知道不?Ontology啊,你們啊,sometime too simple"...不過估計這一天還早得很呢
7. Agile Domain Specified Language : Goodhope
這個也是李默同學有份的項目...話里的敏捷DSL實踐...不過說實話,也有點X...
我常常聽到這樣的觀點:敏捷軟件開發并不是真正的革命性的方法,它所采用的技術大多都是古已有之的。比如迭代,你看很哪本軟件工程的教科書上沒有提到迭代開發呢?在比如說User Story,看上去也不只不過是Use Case的翻版而已吧!甚至我看RUP也和敏捷方法沒有太大的區別吧! 要我說,這些人要么是不真的了解敏捷開發,沒有認識到敏捷開發的革命性,只是用外在的形式來把它和其他方法進行了比較。有又或者是實施敏捷方法的時候不徹底,所以四處碰壁以至于搞起了修正主義。最可怕的就是某些大公司,看敏捷火了,總有包裝一下,到底還是要賣產品。敏捷軟件開發就是一個革命性的方法,只不過它要顛覆的不僅僅是低質量的軟件開發方式,更重要的是,它要顛覆軟件生產企業和軟件的使用企業之間的生產關系!!這一點在敏捷宣言里寫得再明白不過了
Customer collaboration over Contract negotiation
敏捷軟件開發,就是要以一種更合理的共贏的合作關系,代替以前畸形的采購式的合約關系。為什么合約關系就是畸形的?我們來看看合約雙方的處境。
首先軟件團隊方面承擔了過多的風險:業務變化,改代碼!!商業抉擇轉換,改代碼!!憑啥你甲方的緣故非要我承擔額外的成本?你說我冤不冤?冤!但是人家甲方也冤!!人家花了大把的銀子,拿到一堆不能用的軟件(你要是硬件人家還能轉手賣點錢),就像你要砍樹別人給你把鏟子,你要種樹人家給了你把鋸。擱你,你也不愿意。且不說博弈,就算雙方都有心把事情做好,按合同來,甲方不干;不按合同來,乙方不干,最后變成“有心殺賊無力回天”,大家一起扯扯皮等二期算了。lose-lose,沒有贏家。
那么合作的關系是什么呢?合作的關系就好比你去subway買三明治,面包你自己選,要什么肉你來挑,蔬菜,cheese,醬汁你也自己看著辦。技術我來,口味你選。技術失敗我負責,口味不合適你負責。你做你的強項我來我的強項,最終大家高高興興嘻嘻哈哈不吵不鬧,作出一頓可口午餐。這是時候,生產關系變了,我不是你的冷冰冰的供應商,你也不是我邪惡的客戶,我們是拴在一根繩子上的螞蚱。成功是我們的,失敗也是我們的。榮辱與共,攜手并肩。聽著有點耳熟?沒錯,SaaS。敏捷宣言早就說了,CoC啊。從供應商變成服務商,從服務商變成戰略合作伙伴,這是在給軟件企業指出路,新的生產關系已經盡在其中了。
如果看不清敏捷的這個根本革命點,以為還是開發方法的小打小鬧,那么敏捷根本實施不成。這話一般我不敢說的,程序員自發實施敏捷,只在一種情況下可能成功:大企業的IT部門。再趕上個強力的IT領導,自家人嘛,有什么不好談的。一來二去,就成功了(看看C3,說白了不就是IT部門和業務部門?)但是,如果是做項目的公司,你營銷手段不改變,敏捷就不可能成功。你的客戶跟你不是合作關系,你通過敏捷增加質量(符合性質量)的工作就不會被人可,那么就不能成為投資,只能是成本。當成本增加到不可承擔的時候,敏捷就不了了之了。為什么好多人說老板沒有響應?舊的生產關系下敏捷根本就是負擔。
說道這里,說一下以敏捷聞名的ThoughtWorks。其實很多人都以為ThougtWorks只有方法論咨詢,沒錯我們是有方法論咨詢,但是也有業務模式咨詢,客戶業務模式不改變,他怎么能徹底敏捷?這點大家不可不查啊。
Yesterday I found a interesting? ruby library? ——? blinkenlights, which enables you to control the LEDs on your keyboard. I thouhgt it could be a cheap replacement of lava light, so I wrote a ruby script called 'Poor Man's Lava'
#!/usr/local/bin/ruby require 'rss/1.0' require 'rss/2.0' require 'open-uri' require 'rubygems' require 'blinkenlights'
SUCCESS = 'success'
def read_rss source='http://cruisecontrolrb.thoughtworks.com/projects/CruiseControl.rss' ? content = '' ? open(source) do |s| content = s.read end ? rss = RSS::Parser.parse content, false ? rss.items[0].title.include?(SUCCESS) ? all_ok : alarm end
def all_ok times = 50 ? BlinkenLights.open { |lights| times.times {lights.random} } end
def alarm times = 50, invertal = 0.0 ? BlinkenLights.open { |lights| times.times {lights.flash invertal} } end
while true ? read_rss ? sleep 5? end
make sure to have sufficient permissions to access the device, or you could simple run it as super user.
通常人們會將User Story和Use Case放在一起比較,雖然二者在形式上具有一定相似性,但是究其本質來說,還是天淵之別的。這一點,專業BA李默同學總結的格外準確:“用戶故事是可見的商業價值,而不是功能描述”。想要更好的理解這句話,需要了解什么是好的用戶故事。好的用戶故事,可用INVEST原則來概括:
I - Independent N - Negotiable V - Valuable E - Estimable S - Small T - Testable
我個人覺得,這個總結雖好,但不免分散注意。要我說,想把握好User Story,只用把握兩個就夠了Negotiable和Valuable。那么首先要確定什么是Negotiable的。User Story有一個流傳廣泛的書寫形式:
As <role>, I'd like to <action>, so that <benifit/value/goal>.
為了更好的獲取story還有很多最佳實踐,比如personas, 比如business process modeling,其實這些全是糖衣炮彈,As, I'd like to都是噱頭,就是為了把用戶忽悠暈了,然后圖窮匕現直取商業價值和目標。一旦商業價值確定下來,role, action都是可以negotiable。比如李默之前在文章里舉的用戶登錄的例子,輸不輸用戶明密碼?可以商量嘛!是不是只有注冊用戶可以享受個性服務?可以商量嘛!關鍵是用戶想要什么,至于怎么實現這些到頭來都是可以商量的,都是Negotiable。只有客戶的商業價值是不能商量的,也沒的商量。價值沒有了,目標不存在了,這個User Story也就沒用了,商量啥?重寫一張就好了。
因此user story又有另外一個名稱,叫requirement placeholder。就是客戶價值的"立此存照"。至于具體需求,那么就到iteration plan meeting上是商量吧,看看當時什么樣的形式(功能)才是最符合用戶需要。到此,其實大家可以看出來了,user story重點就不再How上,而是在Why上的。有了why,且可Negotiable,把握了精神,你就是按用例來寫需求又有何妨涅?
有了valuable和negotiable的想法墊底,在看看基于user story的初步計劃制定——也就是有名的prioritization——就容易理解多了。用戶根據每張卡的價值,自行比較作出決定,大體場景就跟向神仙許愿一樣。
神仙:我可以滿足你一個愿望。 我:我要榮華富貴!!! 神仙:哦,榮華富貴,那么要不要愛情涅? 我:恩,這個...那我要忠貞的愛情好了!! 神仙:哦,忠貞的愛情,那么要不要健康平安呢? 我:呃.... repeat 無數次,最終我要了一件過冬的皮猴...
自從到了ThouhgtWorks,我訂閱的rss越來越少,很多程度上來說,有了自家的Planet TW,很多相看的blog可以一次性看全了,沒啥太大的必要去看其他的了。這不,在訂閱了Planet TW后的兩年中,我只增訂了一家:InfoQ。呵呵,想不到如今中文站也來了 http://www.infoq.com/cn/。不錯不錯。
pair programing是所有XP實踐中爭議最大的一個,但竊以為確實XP實施的關鍵關鍵實踐之一,甚至于,我認為很多XP實施的失敗都是由于沒有采用pair programming而造成的。 要了解pair為什么重要,就要了解pair的目的在何。當然了,大多數人都知道pair的重點在于知識傳遞,知識共享,持續走查,降低代碼缺陷等等等等。這些都是pair的優點,不過最重要的一點卻常常被忽略——pair programing的最直接而又最根本的目的之一在于simple design。  上圖是著名的Ron Jefferies Model,可以看到XP最佳實踐被劃分成了一個一個的圓圈,而pair, TDD, refactor和simple design位于中心。這并不是說這四個實踐就是xp的核心。jefferies model每一圈代表了xp實踐過程中的不同關注點,最中心的是dev視角,其次是team視角,最外層是交付/管理視角。每圈上的最佳時間多少都有些緊耦合,放開其他的不講,我們專門說說dev圈,pair programing, tdd, refactor和simple design。 這四個實踐里只有simple design最虛也最重要。有一個問題已經被問過無數次了,“到底多simple的design才叫simple”。我對此也有一個近乎刻板的回答:team里所有人員都能夠理解的design就是simple的。一旦立了標準,這四個實踐的主從關系就一下子清晰起來——simple design是這四個實踐的核心,其他三個實踐都是它服務的。 首先做出一個設計,最簡單的判斷標準就是是否可測,一個不可測的設計基本上可以認為無法實現,于是TDD即是simple design的質量保證又是simple design的直覺驗證。 refactor是為了得到好的代碼,那么什么是好的代碼?simple design!!!這里有人不同意了,有人說只是要易于修改和擴展,可是擴展和修改也要別人看得懂才行啊...simple design是起碼的要求嘛。實際上,XP中的refactor就是朝著simple design的方向重構過去的,也就是朝著所有人都能理解的代碼refactor過去的。插一句題外話,為啥說好的架構的不是設計出來的呢?因為好的架構至少應該是simple design的,而simple的概念有和人員相關...所以當你極盡能事show off你的pattern知識之后,得到復雜設計根本就不可能是好的架構。時刻緊記,架構是妥協啊... 最后,pair programming是simple design的實際檢驗!!!因為即便是最復雜的設計,只要是你自己想出來的,你都覺得它簡單無比,里面充滿了直白且顯而易見的理由。可惜不幸的是,我們要的簡單,是對team里所有人的簡單。如果你的pair不能理解你的設計,那么說明你的設計復雜了;如果你們兩個人懂,但是swith pair的時候,換過來的人不懂,說明你的設計復雜了。pair programming(以及他那容易讓人忽略的子實踐switching pair)就是檢驗simple design的過程。pair programing + refactor就是時刻保證simple design防止過渡設計反攻倒算的過程。pair programming + refactor + tdd就是團結在以Deming同學built quality in的質量大旗下,堅定地與過渡設計做斗爭的過程。據我觀察,至沒有使用pair programming的團隊中,少一半simple design成了口號,而這一半中,至少又有一半最終放棄了xp放棄了敏捷(俺以前帶過的團隊就有這樣的...默哀一下)。深刻的教訓啊,我們來高呼一下:"pair programming是檢驗simple design的唯一標準!"。 最后說一下pair programming經濟學,過多的假設我就不講了。單說一點,有哪一位上班的8小時從來不上msn/yahoo/qq等im?有哪一位上班從來不上論壇/不回貼/不發郵件?以我pair的經驗來看,pair programming的過程中,兩個人幾乎不會用im,幾乎不會逛論壇。你不好意思呀,畢竟不是你一個人的機器,畢竟是兩個人的時間,畢竟你也不愿意給同事一種懶散的印象吧?收回的這么浪費的時間,至少頂得過另外一個人的工作時間了吧?
想要理解敏捷軟件開發為什么好,需要從軟件質量講起。那么軟件的質量是什么?這個問題有很多中答案,我們不妨想看看傳統質量理論對于質量是如何理解的。教科書上說,在20世紀質量管理的發展歷程經歷了質量檢驗、統計質量控制和全面質量管理三個階段。其中,質量理念也在不斷的演變。據說有這么幾個階段:
符合性質量 20世紀40年代,符合性質量概念以符合現行標準的程度作為衡量依據,“符合標準”就是合格的產品質量,符合的程度反映了產品質量的水平。比如說我做一個杯子,沒什么特別的要求,也不是我神經質的藝術作品,就是普普通通的一個杯子,那么需要高矮長短,大小胖瘦,等等一干質量屬性,我做好了可以拿著質量標準來對比,一眼就可以看出那里出了什么問題。通過是否符合這些標準判斷產品具有相應的質量。 那么軟件的質量理是不是符合性質量呢?我個人覺得不屬于。雖然我們一樣可以拿出各種各樣的標準,比如故障率,比如bug數等等。但是這些標注都滿足確不一定是好的軟件,比如我寫一個helloworld,雖然他可以沒有bug。但是卻發揮不了任何的作用。這樣的軟件就屬于“高質量”的廢品。正如趙辛梅評價方鴻漸,“你不討厭,但是毫無用處。”,顯然毫無用處的軟件不會是真正高質量的軟件。
適用性質量 20世紀60年代,適用性質量概念以適合顧客需要的程度作為衡量的依據,從使用的角度定義產品質量,認為質量就是產品的“適用性”。是“產品在使用時能夠成功滿足用戶需要的程度”。質量涉及設計開發、制造、銷售、服務等過程,形成了廣義的質量概念。適用性質量的例子也很多,比如我買了一件Givenchy西服(我還真買了一件),但是一時又沒有特別正是的場合(目前還真沒有什么正式的場合),于是我一天四頓牛排(其實只有一頓),于是就吃胖了,這件華麗的Givenchy就穿不上了。那么這件衣服從符合性質量來說,是優質品,但是從適用性質量來說,卻不是一個高質量的產品——因為我穿不上。還有一句話,叫甲之熊掌乙之砒霜。也是適用性質量的標準體現。 那么軟件的質量是不是適用性質量呢?我個人覺得,軟件的質量至少是適用性質量。軟件,尤其是定制軟件/企業軟件,就是量體裁衣。軟件的基本質量就是要在用戶使用的過程中發揮價值,支撐客戶的業務發展。 書上說,從“符合性”到“適用性”,反映了人們在對質量的認識過程中,已經開始把顧客需求放在首要位置。但是它沒說怎么才能做到把客戶需求放到首要位置。我看光靠文檔是堆不出來的,光考說說也是不行的。這個后面講,戴明同學比我講得好。
滿意性質量 20世紀80年代,質量管理進入到TQM階段,將質量定義為“一組固有特性滿足要求的程度”。它不僅包括符合標準的要求,而且以顧客及其他相關方滿意為衡量依據,體現“以顧客為關注焦點”的原則。這個的最典型的例子是麥當勞,他所有的店鋪從風格到食物都保持在同一水平,使你無論在那里,都可以得到一定的購物體驗。也就構成了對麥當勞的滿意性質量的驗證。這個軟件上也是有例子的,內舉不必親,ThoughtWorks大多數項目都可以達到“滿意性質量”,呵呵誰讓俺們是consultant涅。 我隱約覺得滿意性質量應該是一個過程的質量,而不僅僅是軟件的質量,但是目前沒有好的想法,暫且按下不表。
卓越質量 ......下略100字。個人覺得大多數軟件還沒有達到適用性質量,大多是過程也都沒有達到滿意性質量,卓越質量就先不說了吧。
總之,我們大體的認為軟件質量主要是適用性質量起碼是不會錯的。那么怎么才能達到這個質量標準涅?俺是做軟件的,質量管理還是看看Deming同學怎么說吧,不過他老人家的14點總是發生變化。我也只好斷章取義,說說一個敏捷開發人員眼中的14原則:
1. 持之以恒地改進產品和服務 Create constancy of purpose for improvement of product and service
這個很明顯嘛,small release,快速發布,每次發布都是對產品的持續改進。
2.采用新的觀念 Adopt the new philosophy
敏捷啊...
3.停止依靠大規模檢查去獲得質量 Cease dependence on mass inspection
這個還有另一個說法,build quality in。TDD,QA/BA全程參與,都是build quality in的好方法。
4.結束只以價格為基礎的采購習慣 End the practice of awarding business on the basis of price tag alone
這個...貌似是說請咨詢吧...
5.持之以恒地改進生產和服務系統 Improve constantly and forever the system of production and service
這個是敏捷過程的持續改進,對應的實踐大家可能比較陌生——Restrospective!!!
6.實行崗位職能培訓 Institute training on the job
Pair Programming,Learning Lunch敏捷從來都不缺乏學習的機會,就看你有沒有學習的動力了。
7. 建立領導力企業管理 Institute leadership
敏捷團隊的終極目標,自組織團隊,的管理是也。
8. 排除恐懼 Drive out fear
XP第一原則,勇氣,不要恐懼。
9. 打破部門之間的障礙 Break down barriers between staff areas
只有開發團隊的敏捷不是真正的敏捷,敏捷說到底,是將軟件的供求關系從合約型轉為合作型,本來就要是大破障礙。而且障礙不打破,就很難將敏捷實施到底。這也是很多同學嘗試敏捷失敗的原因,僅僅以為敏捷是技術層面上的事情,其實不是。從這個角度來所,敏捷方法的確是深刻而震撼心靈的變革,有些人...呃...敏捷在十月...
10. 取消對員工的標語訓詞和告誡 Eliminate slogans, exhortations, and targets for the work force
恩,什么激情100天...封閉開發...見鬼去吧...不過restrospective的結果是要寫在白板上的,準備時刻改進。自我表揚和自我批評,算不上訓詞吧。
11.取消定額管理和目標管理 Eliminate numerical quotas for the work force. Eliminate management by objectives
很多人都問過我,pair programming了之后,技校怎么辦?嘿嘿,Deming同學已經說了,這樣的考核不要也罷。
12 消除打擊員工工作情感的考評 Remove barriers that rob the hourly worker of his right to pride of workmanship. Remove barriers that rod people in management and in engineering of their right to pride of workmanship
敏捷團隊的自我評價很簡單,360度,由于你幾乎跟所有人都pair過,如果所有人都不說你好...這已經是rp問題了,就不是打擊這么簡單了...
13 鼓勵學習和自我提高? Encourage education and self-improvement for everyone
同前,Pair Programming,Learning Lunch敏捷從來都不缺乏學習的機會,就看你有沒有學習的動力了。
14 采取行動實現轉變 Take action to accomplish the transformation
每次restrospective之后必須定出方案,以實踐改進。而諸位如果想實施敏捷又覺得難于說服領帶,不妨拿Deming同學說說事,這位大老的殺傷力還是曼大的,尤其你老大是MBA的話
很長時間以來我對rails框架本身沒什么興趣,因為我從來都不是web fans,15歲那年我打印了一本HTML Reference準備學習一下,感覺完全nonsense。很長一段時間(大約有3年吧)里基本上看到Markup Language我就會大腦短路。但是我對rails背后的東西很感興趣,是什么讓rails如此有效率,一開始我以為是Ruby DSL,但隨著做的rails項目越來越多,我發現rails的效率的根源來自它對delphi的繼承和發揚。 1.? data centric object model Active Record delphi之所以成功,在于它看準了大部分商用軟件都是數據庫核心的,并為之設計一套相應的框架, delphi的核心就是圍繞數據庫進行開發(李維同學的borland傳奇里寫道,當時之所以起名字叫Delphi,就是因為要taking to Oracle)。而rails也很好的在新時代(Web時代)把握了相似的核心點——content是web的核心,以及數據庫數據到content的映射是動態web的核心。因此它沒有采用所謂的更加嚴肅的ORM技術。而是依然使用了delphi時代的active record(但是進行了object封裝)。如下代碼示范了ruby和delphi在active record實現上的相似 Ruby class?Person?<?ActiveRecord::Base end
x8x?=?Person.new?:name?=>?'x8x' x8x.age?=?15 Delphi people?:?TADOTable;
 begin??? ???people.Table?=?'people'; ???people.InsertRecord('x8x');
???people.First; ???people.FieldByName('age')?:=?15; end 可以看出,Delphi的數據庫味道更濃一些,而ruby則更多使用了對象的視角(我記得在98年前后(我變成oo狂熱分子之后),在寫delphi的時候我基本上不會直接使用Table對象了,而是做一些簡單的封裝,用business object代替直接的數據庫對象。但是最后由于和delphi的ui組件結合的不好而放棄)。但是active record的pattern是一致的。 2. DB(resource)-aware UI component —— Action View Delphi另一個為人稱道的地方是DB-Aware的UI組件,像TDBLabel, TDBMemo還有至今仍位人稱道的TDBGrid,極大的簡化了UI的開發。不過說到底,仍然是Delphi數據庫核心策略的延續。同樣,rails的view helper也是db核心的。text_field之類的可以自動感知active record的內容和錯誤。 <label>Name:</label>?<%=?text_field?'person',?'name'?%> 和 nameLabel?:?TDBLabel;
nameLabel.DataSource?=?peopleTable; nameLabel.Field?=?'name'; nameLabel.Label?=?'Name';
拋開Desktop和web的差異,也可以算是大致相當吧。 3. Simple Component Model —— Plan Object as Component Delphi是基于組件開發,并且是非常成功的一個組件模型。基本上有經驗的delphi程序員,都會在開發過程中抽象幾個VCL component出來簡化自己的開發。一方面是DRY精神,另一方面Delphi簡單的組件模型使得這樣做的代價非常的小。Delphi的組件基本上就是一個對象,重構的過程中修修改改就成組件了。rails其實有類似的機制,而且更簡單直接,更符合web時代的胃口,一個對象外加一個helper就可以成為一個UI組件。與此同時rails還有另外一個天然的同盟——Ruby DSL。把以前Delphi需要用UI設計器的地方都用Ruby DSL代替了。這一點是我最近在用rails做曹老師華麗的RedSaga的時候推行DSL geek主義時發現的。比如現在我有這樣一個tiny DSL用以定義portlet: in controller @portlet?=?Portlet.new?do ????????????name?'administration' ????????????title?'Administration' ????????????tabs?do ????????????????Projects?:controller?=>'projects',?:action?=>?'list' ????????????end ????????end in view <%=?render_portlet?@portlet?%> 這種描述/configuration block風格的dsl與delphi組件的初始化非常相似,或者可以說,只有語法上的差異而無思路上的差異(當然delphi可以借助IDE而不是語言來指定這些,但是這個做法是沒有生產力的)。 with?Portlet?do ???Label?=? ???Name?=? . ???Tabs[0].Controller?=? ???Tabs[1].Action?=? end rails和delphi這種輕量的組件模型,使得構建組件/復用組件級的代價極小。因此可以極大的提高開發效率(我至今仍記得,01年前后接了一個Delphi私活,客戶要求Office XP菜單風格,我找了一個XPMenu的控件,直接仍上去,自己的代碼一行沒改,菜單就Office XP了...)。 總之,Delphi的效率要素Rails大部分都學走了,最后簡單總結一下rails在delphi基礎上的發揚: 1. 用ruby而不是object pascal。語法上更靈活簡單 2. Object Model on top of ActiveRecord,比起Delphi可以跟好的使用OO開發。 3. 組件模型更簡單 4. CoC,這個就不說了 5. expressive Ruby DSL 最后最后,說一下Delphi for PHP,做得很華麗。但是UI部分我不是很喜歡。
I got a Sony PSP recently. After playing with it for a while, I
found out that, comparing to other handheld game console, PSP has a
fairly open platform. Some great guys had already customized a gcc
compiler for PSP, and also, a simple toolchain is provided by the
active community to support porting/development/debuging/hacking. So I
decided to port some of my favourite programming langauges to PSP. Of
course, the first one on my list is Ruby.
Cross compiling,
reading psp documents, reading ruby source code, hacking ruby source
code, cross compiling... after 3 days hard working, finally, I managed
to make the following ruby script(which is listed in the book
<Programming Ruby>) running on my PSP:
def say_goodnight(name)
"Good night, #{name}"
end
puts say_goodnight("PSP")
Bingo~~~! Ruby goes entertainment!!!!
btw : my ruby-psp patch could be found here:
https://rubyforge.org/tracker/index.php?func=detail&aid=8134&group_id=426&atid=1700
make sure you have psp-gcc and toolchain installed on you PC, and 3.03-OE/1.5 fireware on your PSP.
今天被老莊拉到JavaEye扯皮,扯來扯去還是lambda演算...本來應承了老莊寫lambda演算簡介,不過看到磐石T1同學提到了Church number來勾引whl同學...于是我想還是寫一些更有意思的東西吧。 每個Church number都是一個接受兩個參數的函數,這兩個參數又都是函數,第一個參數稱為后繼函數,第二個參數則叫做零點函數。依據這兩個函數,我們可以定義Church number zero, one, two: (define zero? (lambda (successor?zero)?zero)) (define one (lambda (successor?zero)?(successor?zero))) (define two?? (lambda (successor?zero)?(successor?(successor?zero))))
可以看出,所謂one就是對零點函數應用一次后繼函數,而two則是對零點函數應用后繼函數的結果再次應用后繼函數,依次類推可以得到Church Number n。下面我們可以通過后繼函數increase和零點函數f(x) = 0來看看這些Church Number的計算結果: (define?(increase?x)?(+?x?1))
(zero increase 0) > 0 (one increase 0) >1 (two increase 0) >2
an approximate Java version:
public interface Function<T> { ??? T apply(Object... parameters); }
public interface ChurchNumber { ??? Integer apply(Function<Integer> successor, Function<Integer> zero); }
ChurchNumber zero = new ChurchNumber() { ?? public Integer apply(Function<Integer> successor,? Function<Integer> zero) { ????? return zero.apply(); ?? } };
ChurchNumber one = new ChurchNumber() { ?? public Integer apply(Function<Integer> successor, Function<Integer> zero) { ????? return successor.apply(zero); ?? } };
ChurchNumber two = new ChurchNumber() { ?? public Integer apply(Function<Integer> successor, Function<Integer> zero) { ? ? ? return successor.apply(successor.apply(zero)); ?? } };
Function increase = new Function<Integer>() { ?public Integer apply(Object... parameters) { ?? if (parameters[0] instanceof Function) { ????? return ((Function<Integer>) parameters[0]).apply() + 1; ?? } ?? return (Integer) parameters[0] + 1; ?} };
Function numberZero = new Function<Integer>() { ?? public Integer apply(Object... parameters) { return 0;} };
System.out.println(zero.apply(increase, numberZero)); >0 System.out.println(one.apply(increase, numberZero)); >1 System.out.println(two.apply(increase, numberZero)); >2
定義了Church number后,我們繼續定義Church number上的運算,首先是增加1: (define?(inc?x)?(lambda (successor?zero) (successor (x successor zero))))
(define three (inc two)) (three increase 0) >3
an approximate Java version:
static ChurchNumber inc(final ChurchNumber churchNumber) { ?? return new ChurchNumber() { ????? public Integer apply(Function<Integer> successor, Function<Integer> zero) { ??? ???? return successor.apply(churchNumber.apply(successor, zero)); ??? ?? } ?? }; }
ChurchNumber three = inc(two); System.out.println(three.apply(increase, numberZero)); >3
然后是加法: (define?(add?x y)?(lambda (successor?zero)? (x successor (y successor zero))))
(define five (add three two)) (five increase 0) >5
an approximate Java version:
static ChurchNumber add(final ChurchNumber x, final ChurchNumber y) { ??? ??? return new ChurchNumber() { ??? ??? ??? public Integer apply(final Function<Integer> successor, ??? ??? ??? ??? ??? final Function<Integer> zero) { ??? ??? ??? ??? return x.apply(successor, new Function<Integer>() { ??? ??? ??? ??? ??? public Integer apply(Object... parameters) { ??? ??? ??? ??? ??? ??? return y.apply(successor, zero); ??? ??? ??? ??? ??? } ??? ??? ??? ??? }); ??? ??? ??? } ??? ??? }; }
ChurchNumber five = add(two, three); System.out.println(five.apply(increase, numberZero)); >5
最后是乘法: (define?(multiply?x?y) (lambda (successor?zero)? (x? (lambda (z) (y successor z)) zero)))
(define four (multiply two two)) (four increase 0) >4
an approximate Java version:
static ChurchNumber multiply(final ChurchNumber x, final ChurchNumber y) { ??? ??? return new ChurchNumber() { ??? ??? ??? public Integer apply(final Function<Integer> successor, ??? ??? ??? ??? ??? Function<Integer> zero) { ??? ??? ??? ??? return x.apply(new Function<Integer>() { ??? ??? ??? ??? ??? public Integer apply(final Object... parameters) { ??? ??? ??? ??? ??? ??? return y.apply(successor, new Function<Integer>() { ??? ??? ??? ??? ??? ??? ??? public Integer apply(Object... ignoredParameters) { ??? ??? ??? ??? ??? ??? ??? ??? if (parameters[0] instanceof Function) { ??? ??? ??? ??? ??? ??? ??? ??? ??? return ((Function<Integer>) parameters[0]).apply(); ??? ??? ??? ??? ??? ??? ??? ??? } ??? ??? ??? ??? ??? ??? ??? ??? return (Integer) parameters[0]; ??? ??? ??? ??? ??? ??? ??? } ??? ??? ??? ??? ??? ??? }); ??? ??? ??? ??? ??? } ??? ??? ??? ??? }, zero); ??? ??? ??? } ??? ??? }; }
ChurchNumber four = multiply(two, two); System.out.println(four.apply(increase, numberZero));
沒有減法和除法,Church當年發明這套東西的時候就沒有。原因是非常明顯的...因此Church number只有后繼函數,而沒有前驅函數。也就是說Church number只能往前數...不能望后數...自然不可能作出減法和除法了。當然擴展一下也是非常容易的: (define?negative-one?(lambda?(successor?precursor?zero)?(precursor?zero))) (define?one?(lambda?(successor?precursor?zero)?(successor?zero)))
(define?(add?x?y)?(lambda?(successor?precursor?zero)?(x?successor?precursor?(?y?successor?precursor?zero)?)))
(define?(inc?x)?(+?x?1)) (define?(dec?x)?(-?x?1))
(define?zero?(add?one?negative-one))
(zero?inc?dec?0) >0
whl同學問這樣能不能實現浮點,答案是可以實現有限精度的浮點數....因為按照這個思路發展下去,我們定義浮點的successor和precursor函數只能在有限的位數之內...當然有了one,zero再結合pair,模擬0/1存儲實現浮點也不是不可能的事情...
|
|
隨筆:36
文章:0
評論:93
引用:0
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
---|
24 | 25 | 26 | 27 | 28 | 29 | 30 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 1 | 2 | 3 | 4 |
|
常用鏈接
留言簿(21)
隨筆分類
隨筆檔案
搜索
最新評論

閱讀排行榜
評論排行榜
|
|