什么代碼才是好代碼?這真是個老得能拔掉牙齒的話題。好吧,那讓我們再在這刮沙塵暴的無聊時光里重復一次。好的代碼要是易讀的代碼、要做到職責分離、要做到單一職責、要有高的執行效率....
等等,等等,這才抽象了,太書面化了。我只是一個菜鳥,剛寫代碼幾年,也沒念過什么書,能不能說得通俗易懂一些?
好吧,我停下來,想,這真是個難纏的家伙。我說,這樣吧,我推薦幾本書你去看吧,《重構》熊節最近再版了,建議你去買一本。恩,等等,有個省錢的招,去圖靈俱樂部討論組注冊下,蹭贈書也很爽,哈哈。
可是,你還沒告訴我什么代碼才是好代碼呢?知道你也沒什么好答案,我自己來說好了。
省略掉此時我內心花花的汗水,下面是菜鳥的敘述:
1.一致
我發現自己有輕微的強迫癥,當我碰到以下代碼時,我就會沖動。
沖動前代碼:
def imgName
if(XXX){
imgName="meigui"
}else{
imgName=""
}
沖動后代碼:
def imgName=XXX?"meigui":""
盡管兩段代碼功能一致,但一旦我發現出現沖動前代碼時,我就會感到不舒服,感到難受,就好像看到閱兵正步走不齊一樣。方法名也是一樣:
沖動前:
def testXXX(){}
沖動后:
def should_XXX_when_XXX(){}
變量亦是如此:
沖動前:
def imgNode=resouce.adoptTo(Node)
沖動后:
def node=resouce.adoptTo(Node)
總之,我不愿意看到同一個事情有兩種實現方式,如果功能類似,那么不管是邏輯還是變量、方法名,我會強迫一致,整齊劃一。
關于一致,從調試代碼的角度看,零星的不一致比大量的不一致更加糟糕,因為這時大部分地方的一致性會令人麻痹大意。在實現查詢分頁功能時,我們有這樣一行代碼:
nodeIterator.size
這行代碼的意思是獲取查詢結果的總數,大部分情況下它工作良好,但是在一種特殊情況下它返回了-1。這對我當時幾乎是災難性的,因為調試過程中我們始終相信這行代碼的行為一致,結果是花費了一個下午才找到這個問題。
2.簡潔
我喜歡短的代碼,對我而言,短的程序總是比更長一些的代碼容易理解,小學時學課文就已經這樣了,一看到大段的段落我總是會暈過去(特別是文言文,首先我就
對自己理解這段文字失去了信心)。這里要提到注釋,即是這些注釋明確是為了提高代碼的可讀性,也會增加我閱讀代碼的困難,所以我不會在方法里的任何位置添
加注釋,撐死在個別方法聲明前添加,并且這種情況也盡量避免,如果這個類確實包含了重要的不易理解的算法,我也只會在類聲明前添加注釋。
關于自然語言,有一個基于經驗的結論被稱為Zipf定律,即:自然語言中最常用到的單詞,其長度會趨于最短。
我寫代碼的時候,能夠簡寫盡量簡寫,例如,變量名,imageNode,我一定會寫成imgNode;方法名procedureXXX,我一定會寫成
procXXX,和討厭大段代碼一樣,我非常討厭命名很長的方法名和變量名,盡管這些名稱這么長是為了更好的增加可讀性,但可讀性不是這樣增加的。
在我的第一份代碼工作里,我們使用拼音來命名方法和變量(還好,沒有包括類名),我討厭這種命名方式的原因并不是因為我的語文老師不好以至于我前后鼻音不分,而是這種寫法根本排除了簡寫的可能性,甚至,為了避免歧義,有時不得不變得更長。
3.聯覺和順序
關于記憶,人類有兩種重要的記憶能力:聯覺和順序記憶。
關于聯覺,一個例子是:你總可以一眼記住一個人的臉,比如范冰冰,盡管我到現在也不清楚她到底是單眼皮還是雙眼皮,也不清楚她到底是厚嘴唇還是薄嘴唇。
那么,在代碼里,這里的表現就是局部,即一個功能的所有相關代碼都集中在一個地方。我最討厭的代碼是這樣的:最開始我打開一個文件,在閱讀的過程中,我發
現一個不清楚的方法,于是我按下ctrl并點擊鼠標,于是我跳到另外一個文件;接下來,在閱讀另外一個方法里,我再次發現了一個不清楚的方法,于是我再次
按下ctrl并點擊鼠標,哇哈,新的文件打開了....如此反復,終于當我打開最后一個文件時,我發現IDE的文件條里已經密密麻麻的排滿了好幾排文件,
于是,我移動鼠標,右鍵,彈出一個關閉菜單,我選擇了close others,瞬間,哦米拖佛,整個世界清靜了,但是,等等,我最初是打算干嘛來著?
所以,請把所有相關聯的代碼都集中在一個地方,求您了。哦,對了,能不用接口請不要用接口,總會碰到這樣的情況,打開好幾排的文件,接口文件占了一半,我靠,少幾個接口會死啊。對了,這可能是您的一致性心理在作怪,對不起,對不起。
關于局部,一個范例同樣與調試有關,在很久之前的一次調試中,我們始終找不到一個變量錯誤的原因,因為在這段代碼里,根本找不到任何錯誤,很久以后,終于
發現,這個變量竟然是個全局變量,嘿嘿,告訴你吧,這個變量在servlet里,04年的時候,網上很火的一篇文章,標題就是:不要在servlet里使
用全局變量!
關于順序,最典型的例子出現在高中化學里,我總也不能瞬間說出第12、13個化學元素是什么,我通常會這樣記憶:氫氦鋰鈹硼碳氮氧氟氖鈉鎂鋁硅磷,啊哈,第12個元素是鎂,第13個元素時鋁,合起來就是--美女!
所以,在代碼里,請將互相調用的方法按順序擺放,方法1先調用了方法2,那么請將方法2緊放在方法1后邊。我討厭這樣的配置:打開方法1,發現其調用了方
法2,點擊方法2,編輯器里的滾動條瞬間從最上端滾到最下端,緊接著,滾動條又從最下端滾動到中間,再接著,又是最下端,接著,歸零到最上端....人生
經不起這樣的大起大落,真的,那得要多么大的心臟啊,麥蒂才有過那么一次,13秒....
還有,知道為什么goto為什么那么臭名昭著了吧。
4.自然
使得代碼具有輕松的表達方式,同時把錯誤率降到最低,一種最重要的方法就是代碼變得“自然”,即向自然語言靠攏。因為代碼并不僅僅是與機器交流的,更重要的是,需要在人之間交流。
機器語言到高級語言,面向過程語言到面向對象語言,jdbc到hibernate,java到動態語言,這些都促使代碼變得更加自然。
Ruby里有個不起眼的特性,就是方法調用不用再寫括號,這一特性是如此的微不足道但是卻被很多人津津樂道,原因就是它更加自然,更加貼近我們的自然語言。于是,我看到,我的同事曉娜,在groovy里,一遍遍的將她力所能及的括號去掉。
此外,程序語言和自然語言是有區別的,除了不能在代碼里利用感情詞抒發情感之外(我想,如果可以,一定會看到很多的馮特),程序語言沒有口語。很少看到程
序員之間這樣交流,來吧,我們來說段代碼(當然也有,徐昊就可以,哈哈),他們更多的會使用白板和筆或者直接是編輯器。所以,結束招聘時是否需要筆試的爭
論吧,我真為那些不經過筆試就直接招人的公司感到羞愧,因為他們根本就不懂程序語言。
此處省略華麗的分割線。
此文謝謝我們項目組WGSN的激烈討論,謝謝討論中徐昊的精彩點評。
唐僧與 QA MM
在一個典型的項目團隊里,包括了以下幾種角色(帽子): PM(項目經理)、 BA(業務分析師)、 DEV(程序開發者)和 QA(質量保證人員),整個團隊的目標是向客戶交付價值。
那么,有一天, QA MM來找我,我是開發人員。 MM說,一張圖片沒有正常顯示,我想知道原因,同時想知道你能否修復。我的第一想法是,這不可能,一定是環境的原因。我說,好的,稍等。接下來,我張大嘴巴看到了 MM給我重現的 BUG:本該顯示圖片的位置一片空白,就像此時我合不上的嘴。這怎么可能呢?我想,這個功能完成的如此之得意,以至于測試用例里的數據都是以我的名字命名的。
幾分鐘后,或者更長,我叫來 MM,說,找到原因了。
我打開編輯器,光標在源程序的某一行閃爍,我說,最根本的原因在這里。我看到 MM的眼中閃過一絲迷茫。接下來,我卻換到另外一個源文件,光標繼續閃爍,我說,這里的程序因此受到影響。看得出, MM有點發暈。終于,當我打開第 N個源文件并試圖繼續講解時, MM昏過去了。
當 MM蘇醒過來時,我在她清澈的雙眼中看到了一只清晰的唐僧。
MM肯定感到了不好意思,因為我的講解中包含了比喻、類推、排比等我力所能及的各種語文知識,看得出,我很努力,我的語文老師也很努力,所以她委婉地說,能不能簡單一點?
我想了想,說,測試驅動時測試數據不全導致程序少考慮一種情況。
MM說,能修復嗎?
我說,可以。于是故事結束。
就 是這樣,當我們執行一項任務時,圍繞這項任務必然會產生許許多多的信息,這些信息對于該任務的執行者是必須的,但是對于其他人則不是,有效的溝通往往來自 于簡練的表達即只表達對方需要和可以理解的內容,浩瀚的細節只會將真正想表達的內容淹沒。其實這里還有這樣一層意思:我之所以用這么多的細節信息來淹沒 QA,實際上是不太情愿承認程序里有 BUG。 QA想要的結果很簡單,是否是程序 BUG,能否修復。而開發人員則往往把自己的程序與自己關聯在了一起,認為程序是自己的擴展,程序有 BUG則意味著自己有缺陷。這一關系明顯是矛盾的,可是一些團隊里開發人員和 QA能夠和平相處,而有些團隊卻勢如水火。
那么,對于單個任務而言,需要定義自己的變量,這些變量數據只與該任務相關,只在該任務里可見。典型的工作流應用于任務執行期間的中間數據存儲。在文檔處理中,一個重要的功能就是需要提供版本管理,在單個任務實例里,辦理者能夠管理自己處理過的文檔版本。
描述
任務能夠定義變量,在一個流程實例里,該變量只能被其任務實例所使用。
圖 6-2任務級別的數據可見性
如圖 6-2所示,我們在任務 B上定義了一個變量 M,此時,在一個流程實例里,只有任務 B的實例才能使用該變量。
實現
存在兩種實現方式,一種是如圖 6-1所示的,在任務節點定義中聲明變量,運行期初始化任務實例的同時初始化該變量并使用; 另一種是在流程定義級別統一聲明變量,但是各個任務實例都獨立初始化并存儲該變量。第二種實現方式在各個任務都需要使用同一語義的變量時很常見,例如各個任務實例都會有參與者,我們在流程定義時聲明一個名為 userid的變量,在流程實際執行時,各個任務實例都會獨自保存有自己的 userid數據。
和前面的章節一樣,我們先從一個故事開始,這個故事和晚飯有關。在我家,周一至周五,老婆做飯,我洗碗。每天做完 飯,老婆會叫我到廚房,說,看,這個盤要洗一下,另外,灶臺臟了,也要擦。如果放在以前,我會說,好,明白了。但是現在,程序員的生活讓我意識到,溝通永 遠不是一件簡單的事情,我說,好,知道了。
等等,這個故事和本章的主題-數 據模式有一毛錢的關系?這只是一個關于溝通的故事。是的,讓我們稍微映射一下:這里,晚飯這個流程包含了兩個基本的任務,分別是做飯和洗碗,在做飯這個任 務完成時,任務的執行者(老婆)向下一個任務的執行者(我)傳遞了數據(要洗哪些東西),正如語言是人之間的溝通方式一樣,數據是IT系統之間的溝通方式,語言之間的溝通總是最有效的,數據交互卻未必,因為IT系統里的數據交互除了讓計算機理解外重要的是還需要人理解,IT系統是對現實生活的映射,也正因為如此,現在數據之間的溝通也在向語言靠攏即語義化(REST/語義網)。
好,言歸正傳。
在 前兩章里,我們分別討論了工作流的控制模式和資源模式,控制模式關注于如何合理調配業務流程里的任務,從而獲得理想的執行效率和收益;資源模式則關注于如 何合理調配可用的資源來執行業務流程。本章將介紹工作流系統里的數據模式,從數據的角度分析工作流系統對數據的處理。數據模式共計39種,在下面的介紹中,我們將這些模式分為了四部分,分別是數據可見性、數據交互、數據傳輸和基于數據的路由。
本章先會概要重復一下與數據模式相關的一些基本概念,例如流程定義、流程實例、原子任務、塊任務等。接下來會對具體的39種數據模式進行討論,討論的模式按照應用、描述和實現展開,分別對應著實際場景對模式的映射、模式的介紹和工作流系統對該模式的實現支持。最后是小結。
一、基本概念
1、工作流系統里的流程結構
在正式介紹數據模式之前,讓我們先簡單回顧一下工作流系統里流程的基本結構。
圖 6-1 工作流系統里的流程結構
流程定義:對業務流程的建模和描述,其具有足夠的細節信息,能夠直接被工作流系統所執行。典型的,流程定義由一系列的任務組成,這些任務以圖形的形式展現并被連接起來。
流程實例:流程定義的一個執行實例被稱為流程實例。一個流程定義可以存在多個同時執行的流程實例。這些流程實例互相獨立執行。
任務:一個任務對應著流程定義里的一個單一工作單元。存在四種不同類型的任務:原子任務、塊任務、多實例任務和多實例塊任務。
原子任務包含簡單且獨立的任務定義,當其初始化時只會產生一個可執行的任務實例。
塊任務是一系列任務的組合,典型的,工作流系統里存在的子流程任務(節點)即是塊任務,當一個塊任務開始執行時,其將流程控制權傳遞給與之對應子流程的第一個任務,當子流程完成執行后則將控制權返回給塊任務。如圖6-1所示,塊任務C對應著一個由任務X、任務Y和任務Z組成的子流程,實際執行時,任務C會觸發任務X的執行,任務Z執行完畢即子流程執行完成后則會觸發任務C執行完成。
多實例任務在實際執行時會產生多個并行執行的任務實例,這些任務實例一般互相獨立執行。當一定數量的實例執行完畢后即會觸發后續任務的執行。
多實例塊任務結合了塊任務和多實例任務的定義,其在實際執行時產生多個任務實例,每個任務實例對應著一個子流程實例。
任務實例:任務的一個執行實例。
2、數據相關約定
我們使用def var ${變量名}定義數據元素,同時def var ${變量名}的聲明位置決定了變量的作用范圍。如圖6-1所示,我們在任務C上定義了一個名為M的數據變量,其的作用范圍為任務C,任務級別。
我們使用use(${變量名})表明對變量的使用;使用pass(${變量名})表明數據變量的傳遞。在圖6-1里,變量M從任務C傳遞至任務E。
對于變量的數據類型,典型的有String、integer、float、boolean、date、time等,很多工作流系統使用序列化和反序列化支持存儲任意類型的數據類型,如數組、集合、對象。
3、類比的約定
在后續對各個模式的介紹里,我會圍繞一個項目團隊的故事進行映射,我們如此約定:
流程定義:我們認為所有成熟的軟件公司都會建立起其完整并適用的一套軟件開發流程,我們將這套流程看作是這里的流程定義。
流程實例:圍繞著軟件開發流程,我們會使用這套流程來開始我們實際的軟件開發項目,我們將所有的軟件開發項目都看作是開發流程的執行實例,即流程實例。
任務:項目開發過程中的各項任務。
數據:我們將團隊成員之間的信息交流看作是數據交互。
我們從一個最簡單的登錄例子開始。最開始我們需要驗證在用戶名和密碼都正確的情況下,能夠正常登錄系統,我們這樣編寫測試代碼(以下都是偽代碼,使用TestNG和Selenium):
def should_login_success_with_exist_username_and_correct_password(){
LoginPage page = user.open(LoginPage,"/login.html")
user.perform("login",['user1','1234'],on(page))
assert page.successLogin
}
恩,很不錯,運行一下,出現紅條。為什么呢?原來測試數據庫里沒有用戶名為user1的用戶,好吧,寫個數據庫數據初始化腳本。再運行,OK,綠條!
那么,接下來我們再增加一個測試,需要覆蓋密碼錯誤時不能登錄系統的情況,很快測試就完成了:
@Test
def should_login_success_with_exist_username_and_incorrect_password(){
LoginPage page = user.open(LoginPage,"/login.html")
user.perform("login",['user1','4321'],on(page))
assert page.successLogin,false
}
再運行一下測試,綠條。好啦,現在可以看下這段代碼,恩,有些重復,重構一下:
def should_login_success_with_exist_username_and_correct_password(){
assert login('user1','1234')
}
@Test
def should_login_success_with_exist_username_and_incorrect_password(){
assert login('user1','4321'),false
}
def login(username,password){
LoginPage page = user.open(LoginPage,"/login.html")
user.perform("login",[username,password],on(page))
return page.successLogin
}
重構完成,可以看到,我們的測試方法里現在沒有了任何行為,僅僅是數據!這樣讓我感覺有點怪,不管了,先用TestNG提供的@dataProvider整理一下:
def testLogin(username,password,expected){
LoginPage page = user.open(LoginPage,"/login.html")
user.perform("login",[username,password],on(page))
assert page.successLogin,expected
}
@DataProvider(name="testdata")
def Object[][] dataForLogin(){
def data=new Object[2][]
data[0]=['user1','1234',true] as Object[]
data[1]=['user1','4321',false] as Object[]
}
測試方法只剩下了一個!如果要測試不存在的用戶不能登錄系統呢?很簡單,增加數據即可!
def Object[][] dataForLogin(){
def data=new Object[2][]
data[0]=['user1','1234',true] as Object[]
data[1]=['user1','4321',false] as Object[]
data[1]=['inexistuser','1234',false] as Object[]
}
在我們的測試方法里,測試數據和測試的行為進行了完全的分離。從系統的功能來說,功能一旦實現,那么就是一個黑盒,我們只要提供數據即可進行測試,這個數據包括兩部分:輸入和期待的輸出。我開始暗自嘀咕:難道我以前那么多的洋洋得意測試方法很多都是不需要的嗎?這些方式為什么會存在呢?恩,想起來了,這些方法是為了覆蓋功能的各個路徑的,是提高測試覆蓋率的。那么為什么會產生這么多的測試方法呢?哦,在這些測試方法里,測試數據和測試行為是耦合在一起的!
我伸了個懶腰,突然想,這下好了,我已經封裝好了功能行為,QA想增加測試用例只需要自己增加數據就可以了,嘿嘿,爽啊。說曹操,QA到。QA mm說,你的某個功能實現有問題。我瞅了一眼,說,不可能?。ㄟ@是dev面對bug的第一反應),俺有測試的,持續集成一直是綠條的。QA mm說,在你的開發環境下測試是沒有問題的,但是在QA環境,因為數據庫變了,數據變了,應用服務器變了,所以會有些問題。我極不情愿的登錄到QA環境,一測試,還真是,郁悶。
怎么辦?修復完BUG,第一反應就是自動化測試能不能跑在QA環境呢?一般情況下,這些測試需要干凈的測試環境,我們會制造很多的測試數據,可是在QA環境下,QA有她自己的測試數據,這些數據都不存在了哈。恩,看看剛才的測試代碼,哈,就用QA的數據也可以啊,心情愉悅的改下:
def Object[][] dataForLogin(){
def data=new Object[2][]
data[0]=['hrong','1234',true] as Object[]
data[1]=['hrong','4321',false] as Object[]
data[1]=['rhao','1234',false] as Object[]
}
OK,完成!為了該測試既能在開發測試環境運行又能在QA環境下運行,我們可以引入一個環境變量,將測試數據扔到文件里,通過環境變量來加載不同的測試數據(測試文件)。
好吧,喝點東西(甲流很厲害,喝板藍根好了),總結一下:
數據驅動測試:測試數據與測試行為分離,通過數據來驅動測試。
好處:在對測試行為封裝好的情況下,QA mm能夠自己通過數據修改自動化測試;
自動化測試能夠運行在多個環境下(開發環境、QA環境、產品環境)
測試的可讀性
測試方法大量壓縮
適用范圍:功能測試(selenium測試)
通過環境準備測試數據(非測試用例自己準備數據)
可能存在的問題:比一般的測試編寫困難,特別是在靜態語言里
最后:該文章的思考來自于徐昊在團隊內部的相應Session.
只提一個假設,如果谷歌占據了中國80%的搜索市場份額,google的高管,還會這么高調的宣稱要do no evil,從中國退出嗎?
整個事情給我的唯一感受,就是惡心。
————–
以上是作為一個曾經的忠實google用戶而說的,和百度無關。市面上沾沾自喜于了解一點google的產品技術細節將google奉為道德楷模而自封G粉的兄弟,請勿跟帖瞎噴,你們根本不懂什么叫搜索引擎,什么叫自由人權。
立此存照的原因在于,原帖 http://news.csdn.net/a/20100113/216459.html 被百度的人要求刪掉,所以本著對歷史負責的態度,保存與此,歡迎轉帖。
| |||||||||
日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
---|---|---|---|---|---|---|---|---|---|
25 | 26 | 27 | 28 | 29 | 30 | 31 | |||
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 | 1 | 2 | 3 | 4 | 5 |
常用鏈接
留言簿(38)
隨筆分類
- ajax相關(9)
- cms(7)
- Head First Process-深入淺出流程(15)
- j2se基礎(6)
- JbpmSide(6)
- OOA/OOD(4)
- SOA、BPM(26)
- 工作日志(24)
- 工作流jbpm3(10)
- 張小慶,在路上(42)
- 心情小站(24)
- 權限相關(12)
- 表現層相關(4)
- 轉載(4)
隨筆檔案
- 2013年8月 (1)
- 2012年12月 (1)
- 2012年1月 (3)
- 2011年12月 (2)
- 2011年11月 (2)
- 2011年10月 (3)
- 2011年9月 (3)
- 2011年8月 (7)
- 2011年7月 (4)
- 2011年6月 (3)
- 2011年5月 (5)
- 2011年4月 (6)
- 2011年3月 (4)
- 2011年2月 (2)
- 2010年9月 (1)
- 2010年6月 (1)
- 2010年5月 (1)
- 2010年3月 (4)
- 2010年1月 (2)
- 2009年11月 (5)
- 2009年10月 (4)
- 2009年9月 (1)
- 2009年7月 (1)
- 2009年6月 (2)
- 2009年5月 (2)
- 2009年4月 (1)
- 2009年3月 (4)
- 2009年2月 (2)
- 2008年12月 (1)
- 2008年11月 (1)
- 2008年10月 (1)
- 2008年9月 (2)
- 2008年8月 (2)
- 2008年7月 (2)
- 2008年6月 (3)
- 2008年5月 (4)
- 2008年4月 (1)
- 2008年3月 (2)
- 2008年2月 (2)
- 2008年1月 (4)
- 2007年11月 (3)
- 2007年10月 (3)
- 2007年9月 (2)
- 2007年8月 (4)
- 2007年7月 (1)
- 2007年6月 (12)
- 2007年5月 (2)
- 2007年4月 (1)
- 2007年3月 (8)
- 2007年2月 (6)
- 2007年1月 (4)
- 2006年12月 (4)
- 2006年11月 (3)
- 2006年10月 (1)
- 2006年8月 (2)
- 2006年7月 (3)
- 2006年6月 (3)
- 2006年4月 (1)
- 2006年3月 (2)
- 2006年2月 (2)
- 2006年1月 (4)
- 2005年12月 (7)
- 2005年11月 (12)
文章分類
文章檔案
常去的網站
搜索
最新評論

- 1.?re: 使用Handler來增強Web服務的功能
- asdfasfd
- --ads
- 2.?re: 使用solr搭建你的全文檢索
-
@木哥哥
你的分詞器用的是什么???mmseg貌似可以的 - --陳冠馳
- 3.?re: 使用solr搭建你的全文檢索
-
@marten這是你的solr的schame.xml配置文件有問題。好好檢查下你的配置文件里面的字段什么的配置對著沒
- --陳冠馳
- 4.?re: 討論一下你覺得一個工作流產品好的標準
- 評論內容較長,點擊標題查看
- --深圳非凡信息技術有限公司
- 5.?re: DisplayTag應用
- name="test"從哪里來的,千篇一律的到處使用test卻沒有test的定義,sb
- --qige