日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

高效程序员的45个习惯

發布時間:2023/12/14 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 高效程序员的45个习惯 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
本文轉載 《高效程序員的45個習慣》一書目錄 第1章 敏捷---高效軟件開發之道 第2章 態度決定一切 1.做事 2.欲速則不達 3.對事不對人 4.排除萬難,奮勇前進 第3章 學無止境 5.跟蹤變化 6.對團隊投資 7.懂得丟棄 8.打破沙鍋問到底 9.把握開發節奏 第4章交付用戶想要的軟件 10.讓客戶做決定 11.讓設計指導而不是操縱開發 12.合理地使用技術 13.保持可以發布 14.提早集成,頻繁集成 15.提早實現自動化部署 16.使用演示獲得頻繁反饋 17.使用短迭代,增量發布 18.固定的價格就意味著背叛承諾 第5章 敏捷反饋 19.守護天使 20.先用它再實現它 21.不同環境,就有不同問題 22.自動驗收測試 23.度量真實的進度 24.傾聽用戶的聲音 第6章 敏捷編碼 25.代碼要清晰地表達意圖 26.用代碼溝通 27.動態評估取舍 28.增量式編程 29.保持簡單 30.編寫內聚的代碼 31.告知,不要詢問 32.根據契約進行替換 第7章 敏捷調試 33.紀錄問題解決日志 34.警告就是錯誤 35.對問題各個擊破 36.報告所有的異常 第一章 敏捷--高效軟件開發之道不管路走了多遠,錯了就要重新返回.------土耳其諺這句土耳其諺語的含義顯而易見,你也會認同這是軟件開發應該遵守的原則.但很多時候,開發人員(包括我們自己)發現自己走錯路后, 卻不愿意立即回頭,而是抱著遲早會步入正規的僥幸心理,繼續錯下去.人們會想,或許差不多少吧,或許錯誤不像想象的那么嚴重.假使開發 軟件是個確定的,線性的過程,我們隨時可以撤回來,如同諺語中所說的那樣.然而,它卻不是.相反,軟件開發更像是在沖浪----一直處于動態、不斷變化的環境中.大海本身無法預知,充滿風險,并且海里還可能有鯊魚出沒.沖浪之所以如此有挑戰性,是因為波浪各不同.在沖浪現場,每次波浪都是獨一無二的,沖浪的動作也會各不相同.例如,海灘邊的波浪和峭 壁下的海浪就是很大的區別.在軟件開發領域,在項目研發過程中出現的需求變化,像波浪一樣.在不同的業務領域和應用下,軟件項目具有不同的形式,帶來了不同的 挑戰.甚至還有鯊魚以各種偽裝出沒. 軟件項目的成敗,依賴與整個項目團隊中所有開發成員的技術水平,對于他們的培訓,以及他們各自的能力高低.就像成功的沖浪手一樣, 開發人員必須也是技術扎實、懂得掌握平衡和能夠敏捷行事的人.不管是預科之外的波浪沖擊,還是預想不到的設計失敗,在這兩種情況下敏 捷都意味著可以快速地適應變化. 敏捷的精神那么,到底什么是敏捷開發方法?整個敏捷開發方法的運動從何而來呢?2001年2月,17位志愿者(包括作者之一Andy在內)聚集在美國猶他州雪鳥度假勝地,討論一個新的軟件開發趨勢,這個趨勢被不嚴格地成為"輕量型軟件開發過程". 我們都見過了因為開發過程的冗余、笨重、繁雜而失敗的項目.世上應該有一種更好的軟件開發方法---只關注真正重要的情況,少關注那些占用大量時間而無甚裨益的不重要的情況. 這些志愿者們給這個方法學取名為敏捷.他們審視了這種新的軟件開發方法,并且發布了敏捷開發的宣言:一種把為人為本、團隊合作、快速響應變化和可工作的軟件作為宗旨的開發方法(本頁最開始的方框就是宣言的內容). 敏捷開發可以快速地響應變化,它強調團隊合作,人們專注于具體可行的目標(實現真正可以工作的軟件),這就是敏捷的精神.它打破了那種基于計劃的瀑布式軟件開發方法,將軟件開發的實際重點轉移到一種更加自然和可持續的開發方式上. 它要求團隊中的每一個人(包括與團隊合作的人)都具備職業精神,并積極地期望項目能夠獲得成功.它并不要求所有人都是有經驗的專業人員,但必須具有專業的工作態度----每個人都希望盡最大可能做好自己的工作. 如果在團隊中經常有人曠工、偷懶甚至直接怠工,那么這樣的方法并不適合你,你需要的是一些重量級的、緩慢的、低生產率的開發方法.如果情況并非如此,你就可能用敏捷的方式進行開發. 這意味著你不會在項目結束的時候才開始測試,不會在月底才進行一次系統集成,也不會在一開始編碼的時候就停止收集需求和反饋. 相反,這些活動會貫穿項目的整個生命 周期.事實上,只要有人繼續使用這個軟件, 開發就沒有真正結束.我們進行的是持續開 發、持續反饋.你不需要等到好幾個月之后才發現問題:越早發現問題,就越容易修復問題,所以應該就在此時此刻把問題修復. 這就是敏捷的重點所在 這種持續前進的開發思想根植于敏捷方法中.它不但應用于軟件開發的生命周期,還應用于技術技能的學習、需求采集、成品部署、用戶培訓等方面.它包括了軟件開發各個方面的所以活動. 為什么要進行持續開發呢?因為軟件開發是一項 非常復雜的智力活動,你遺留下來的任何問題,要 么僥幸不會發生意外,要么情況會變得更糟糕,慢 慢惡化直到變得不可控制.當問題累積到一定程度的時候,事情就更難解決,最后無法扭轉.面對這樣的問題,唯一有效的解決辦法就是 持續地推進系統前進和完善(見《程序員修煉之道》一書中的"軟件熵"[HTOO]). 有些人對使用敏捷方法有顧忌,認為它只是另一種危機管理而已.事實并非如此.危機管理是指問題累計并且惡化,直到它們變得非常嚴重,以至于你不得不立即放下一切正在做的工作來解決危機.而這樣又會帶來其他的負面影響,你就會陷入危機和恐慌的惡心循環中.這些正是你要避免的問題. 所以,你要防微杜漸,把問題解決在萌芽狀態,你要探索未知領域,在大量成本投入之前先確定其可行性.你要知錯能改,在事實面前主動承認自己的所有錯誤.你要能自我反省,經常編碼實戰,加強團隊協作精神.一開始你可能會覺得不適應,因為這同以往有太多的不同,但是只要能真正地行動起來,習慣了,你就會得心應手. 敏捷的修理之道 下面一句話是對敏捷的精辟概括.下面講扼要講述它的具體含義,以及敏捷的團隊應該采取什么樣的工作和生活方式。 首先,它要整個團隊一起努力。敏捷團隊往往是一個小型團隊,或者是大團隊分成的若干小團隊(10人左右).團隊的所有成員在一起工作,,如果可能,最好有獨立的工作空間(或者類似bull pen),一起共享代碼和必要的開發任務,而且大部分時間都能在一起工作.同時和客戶或者軟件的用戶緊密工作在一起,并且盡可能早且頻繁地給他們演示最新的系統.(注:bull pen 原指在棒球比賽中,候補投手的練習場) 你要不斷從自己寫的代碼中得到反饋,并且使用自動化工具不斷地構建(持續集成)和測試系統.在前進過程中,你都會有意識地修改一些代碼:在功能不變的情況下,重新設計部分代碼,改善代碼的質量.這就是所謂的重構,它是軟件開發中不可或缺的一部分---編碼永遠沒有真正意義上的"結束".要以迭代的方式進行工作:確定一小塊時間(一周左右)的計劃,然后按時完成它們.給客戶演示每個迭代的工作成果,及時得到他們的反饋(這樣可以保證方向正確),并且根據實際情況盡可能頻繁地發布系統版本讓用戶使用.對上述內容有了了解之后,我們會從下面幾個方面更深入地走進敏捷開發的實踐. 第2章: 態度決定一切. 軟件開發是一項智力勞動.在此章,我們會講解如何使用敏捷的心態開始工作,以及一些有效的個人習慣.這會為你使用敏捷方法打下扎實的基礎. 第3章:學無止境. 敏捷項目不可能坐享其成.除了開發之外,我們還有在幕后進行其他的訓練,雖然它不屬于開發工作本身,但卻對團隊的發展極其重要.我們還將看到,如何通過培養習慣來幫助個人和團隊成長并自我超越. 第4章:交付用戶想要的軟件. 如果軟件不符合用戶的需求,無論代碼寫得多么優美,它都是毫無用處的.這里將會介紹一些客戶協作的習慣和技巧,讓客戶一直加入到團隊的開發中,學習他們的業務經驗,并且保證項目符合他們的真正需求. 第5章:敏捷反饋. 敏捷團隊之所以能夠順利開展工作,而不會陷入泥潭掙扎導致項目失敗,就是因為一直使用反饋來糾正軟件和開發過程.最好的反饋源自代碼本身.本章將研究如何獲得反饋,以及如何更好地控制團隊進程和性能. 第6章:敏捷編程. 為滿足將來的需求而保持代碼的靈活和可變性,這是敏捷方法成功的關鍵.本章給出了一些習慣,介紹如何讓代碼更加整潔,具有更好的擴展性,防止代碼慢慢變壞,最后變得不可收拾. 第7章:敏捷調試.調試錯誤會占用很多項目開發的時間---時間是經不起浪費的.這里將會學到一些提高調試效率的技巧,節省項目的開發時間. 第8章:敏捷協作: 最后,一個敏捷開發者已經能夠獨當一面,除此之外,你需要一個敏捷團隊.這里有一些最有效的實踐有助于黏合整個團隊,以及 其他一些實踐有助于團隊日常事務和成長. 魔鬼和這些討厭的細節 如果你翻翻這本書就會注意到,在每節的開頭我們都會引入一段話,旁邊配有一個魔鬼木刻像,誘使你養成不良習慣,如下所示. "干吧,就走那個捷徑.真的,它可以為你節省時間.沒人會知道是你干的,這樣你就會加快自己的開發進度,并且能夠完成這些任務了.這就是關鍵所在." 他的有些話聽上去有點兒荒唐,就像是Scott Adams筆下呆伯特(Dilbert)漫畫書中的魔王---"尖發老板"所說的話一樣.但要記住Adams先生可是從他那些忠實的讀者中得到很多回饋的. 有些事情看上去就會讓人覺得很怪異,但這全部是我們親耳所聞、親眼所見,或者是大家秘而不宣的事情,它們都是擺在我們面前的誘惑,不管怎樣,只要試過就會知道,為了節省項目的時間而走愚蠢的捷徑是會付出巨大代價的. 與這些誘惑相對,在每個習慣最后,會出現以為守護天使,由她給出我們認為你應該遵循的一些良策.先難后易. 我們首先要解決困難的問題,把簡單的問題留到最后. 現實中的事情很少是黑白分明的.我們將用一些段落描述一個習慣應該帶給你什么樣的切身感受,并介紹成功實施和保持平衡的技巧.如下所示 切身感受 本段描述培養某個習慣應該有什么樣的切身感受,如果在實踐中沒有這樣的體會,你就要考慮改變一下實施的方法. 平衡的藝術 □ 一個習慣很可能會做得過火或者做得不夠.我們會給出一些建議,幫你掌握平衡,并告訴你一些技巧,能使習慣真正為你所用.畢竟,一件好事做得過火或者被誤用,都是非常危險的(我們見過很多所謂的敏捷項目最后失敗,都是因為團隊在實踐的時候沒有保持好自己的平衡).所以,我們希望保證你能真正從這些習慣中獲益. 通過遵循這些習慣,把握好平衡的藝術,在真實世界中有效地應用它們,你將會看到你的項目和團隊發生了積極的變化. 好了,你將步入敏捷開發者的修煉之路,更重要的是,你會理解其后的開發原則. 第2章 態度決定一切 選定了要走的路,就是選定了它通往的目的地. ----Harry Emerson Fosdick(美國基督教現代主義神學家) 傳統的軟件開發圖書一般先介紹一個項目的角色配置,然后是你需要產生哪些工件(artifact)----文檔、任務清單、甘特(Gantt)圖等,接著就是規則制,往往是這么寫的:汝當如此(注:或更通俗地寫成:系統應當如何如何....)這般.....本書的風格不是這樣的.歡迎進入敏捷方法的世界,我們的做法有些不同. 例如,有一種相當流行的軟件方法學要求對一個項目分配35種不同的角色,包括架構師、設計人員、編碼人員、文檔管理者等.敏捷方法卻背道而馳.只需要一個角色:軟件開發者,也就是你.項目需要什么你就做什么,你的任務就是和緊密客戶協作,一起開發軟件.敏捷依賴人,而不是依賴于項目中的甘特圖和里程表. 圖表、集成開發環境或者設計工具,它們本身都無法產生軟件,軟件是從你的大腦中產生的.而且它不是孤立的大腦活動,還會有許多其他方面的因素:個人情緒、辦公室的文化、自我主義、記憶力等.它們混為一體,態度和心情的瞬息變化都可能導致巨大的差別. 因此態度非常重要,包括你的和團隊的.專業的態度應該著眼于項目和團隊的積極結果,關注個人和團隊的成長,圍繞最后的成功開展工作.由于很容易變成追求不太重要的目標,所以在本章,我們會專注于那些真正的目標.集中精力,你是為做事而工作.(想知道怎么做嗎?請見下一頁.) 軟件項目時常伴有時間壓力---壓力會迫使你走捷徑,只看眼前利益.但是,任何一個有經驗的開發者都會告訴你,欲速則不達(我們在第15頁將介紹如何避免這個問題). 我們每個人或多或少都有一些自我主要.一些人(暫且不提他們的名字)還美其名曰"健康"的自我主義.如果要我們去解決一個問題,我們會為完成任務而感到驕傲,但這種驕傲有時會導致主觀和脫離實際.你也很可能見過設計方案的討論變成人身攻擊,而不是就事論事地討論問題.對事不對人(第18頁)會讓工作更加有效. 反饋是敏捷的基礎.一旦你意識到走錯了方向,就要立即做出決策,改變方向.但是指出問題往往沒有那么容易,特別當它涉及一些政治因素的時候.有時候,你需要勇氣去排除萬難,奮勇前進(第23頁) 只有在你對項目、工作、事業有一個專業的態度時,使用敏捷方法才會生效.如果態度不正確,那么所有的這些習慣都不管用.有了正確的態度,你才可以從這些方法中完全受益.下面我們就來介紹這些對你大有裨益的習慣和建議. 1.做事 "出了問題,第一重要的是確定元兇.找到那個白癡!一旦證實了是他出的錯誤,就可以保證這樣的問題永遠不會再發生了." 有時候,這個老魔頭的話聽起來似乎很有道理.毫無疑問,你想把尋找罪魁禍首設為最高優先級,難道不是嗎?肯定的答案是:不.最高優先級應該是解決問題. 也許你不相信,但確實有些人常常不把解決問題放在最高優先級上.也許你也沒有.先自我反省一下,當有問題出現時,"第一"反應究竟是什么. 如果你說的話只是讓事態更復雜,或者只是一味地抱怨,或者傷害了他人的感情,那么你無意中在給問題火上澆油.相反,你應該另辟蹊徑,問問"為了解決或緩解這個問題,我能夠做些什么?"在敏捷的團隊中,大家的重點是做事.你應該把重點放到解決問題上,而不是在指責犯錯者上糾纏. 世上最糟糕的工作(除了在馬戲團跟在大象后面打掃衛生)就是和一群愛搬弄是非的人共事.他們對解決問題并沒有興趣,相反,他們愛在別人背后 議論是非.他們挖空心思指手畫腳,議論誰應該受到指責.這樣一個團隊的生產力是極其低下的.如果你發現自己在這樣的團隊中工作,不要從團隊中走 開----應該跑開.至少要把對話從負面的指責游戲引到中性的話題,比如談論體育運動(紐約揚基隊最近怎么樣)或者天氣. 在敏捷團隊中,情形截然不同.如果你向敏捷團隊中的同事抱怨,他們會說:"好,我能幫你做些什么呢?"他們把精力直接放到解決問題上,而不是抱怨.他們的動機很明確,重點就是做事.不是為了自己的面子,也不是為了指責,也無意進行個人智力角斗. 你可以從自己先做起.如果一個開發者帶著抱怨或問題來找你,你要了解具體的問題,詢問他你能提供什么樣的幫助.這樣簡單的一個行為就清晰地表明你的目的是解決問題,而不是追究責任,這樣就會消除他的顧慮.你是給他們幫忙的.這樣,他們會知道每次走進你的時候,你會真心幫助他們解決問題.他們可以來找你把問題解決了,當然還可以繼續去別處求助. 如果你找人幫忙,卻沒人積極響應,那么你應該主動引導對話.解釋清楚你想要什么,并清晰地表明你的目的是解決問題,而不是指責他人或者進行爭辯. 指責不會修復bug. 把矛頭對準問題的是解決辦法,而不是人.這是真正有用處的正面效應. 切身感受 勇于承認自己不知道答案,這會讓人感覺放心.一個重大的錯誤應該被當作一次學習而不是指責他人的機會.團隊成員們在一起工作,應該互相幫助,而不是互相指責. 平衡的藝術 □ "這不是我的錯",這句話不對."這就是你的錯",這句話更不對. □ 如果你沒有犯過任何錯誤,就說明你可能沒有努力去工作. □ 開發者和質量工程師(QA)爭論某個問題是系統本身的缺陷還是系統增強功能導致的,通常沒有多大的意義.與其如此,不如趕緊去修復它. □ 如果一個團隊成員誤解了一個需求、一個API調用,或者最近一次會議做的決策,那么,也許就以為這團隊的其他成員也有相同的誤解.要確保整個團隊盡快消除誤解. □ 如果一個團隊成員的行為一再傷害了團隊,則他表現得很不職業.那么他就不是在幫助團隊向解決問題的方向前進.這種情況下,我們必須要求他離開這個團隊.(不需要解雇他,但是他不能繼續留在這個項目中.同時也要意識到,頻繁的人員變動對整個團隊的平衡也很危險) 2. 欲速則不達 "你不需要真正地理解那塊代碼,它只要能夠工作就可以了.哦,它需要一個小小的調整.只要在結果中再加上幾行代碼,它就可以工作了.干吧!就把那幾行代碼加進去,它應該可以工作." 我們經常會遇到這種情況,出現了一個bug,并且時間迫切.快速修復確實可以解決它---只要新加一行代碼或者忽略那個列表上的最后一個條目,它就可以工作了.但接下來的做法才能說明,誰是優秀的程序員,誰是拙劣的代碼工人. 拙劣的代碼工人會這樣不假思索地改完代碼,然后快速轉向下一個問題. 優秀的程序員會挖掘更深一層,盡力去理解為什么這里必須要加1,更重要的是,他會想明白會產生什么其他影響. 也許這個例子聽起來有點做作,甚至你會覺得很無聊.但是,真實世界中有大量這樣的事情發生.Andy以前的一個客戶正遇到過這樣的問題.沒有一個開發者或者架構師知道他們業務領域的底層數據模型.而且,通過幾年的累計,代碼里有成千上萬的+1和-1修正.在這樣臟亂的代碼中添加新的功能或者修復bug,就難逃脫發的噩運(事實上,很多開發者因此而禿頂). 千里之堤,毀于蟻穴,大災難是逐步演化來的.一次又一次的快速修復,每一次都不探究問題的根源,久而久之就形成了一個危險的沼澤地,最終會吞噬整個項目中的生命. 在工作壓力下,不去深入了解真正的問題以及可能的后果, 就快速修改代碼,這樣只是解決表面問題,最終會引發大問題. 快速修復的誘惑,很容易令人把持不住,墜入其中.短期看,它 似乎使有效的.但從長遠來看,無異于穿越一片沙流,你也許僥幸走過了一半的路程(甚至更遠),一切似乎都很 正常.但是轉眼間悲劇就發生了.... 只要我們繼續進行快速修復,代碼的清晰度就不斷降低.一旦問題累計到一定程度,清晰的代碼就不復存在,只剩一片渾濁.很可能在你的公司就有人這樣告訴你:"無論如何,千萬不能碰那個模塊的代碼.寫代碼那哥們已經不在這兒了,沒有人看得懂他們的代碼."這些代碼根本沒有清晰度可言.它已經成為一團迷霧,無人能懂. 如果你在團隊中這樣的事情發生,那么你是不可能敏捷的.但是敏捷方法中的一些技術可以阻止這樣的事情發生.這里只是一些概述,后面的章節會有更深入的介紹. 孤立非常危險,不要讓開發人員完全孤立地編寫代碼(見第155頁,習慣40).如果團隊成員花些時間閱讀其他同事寫的代碼,他們就能確保代碼是可 讀和可理解的,并且不會隨意加入這些"+1或-1"的代碼.閱讀代碼的頻率越高越好.實行代碼復審,不僅有助于代碼更好理解,而且是發生bug最有效的方法之一(見第165頁,習慣44). 另一種防止代碼難懂的重要技術就是單元測試.單元測試幫助你很自然地把代碼分層,分成很多可管理的小塊,這樣就會得到設計更好,更清晰的代碼.更深入項目的時候,你可以直接閱讀單元測試---它們是一種可執行的文檔(見第78頁,習慣 19).有了單元測試,你會看到更小、更易于理解的代碼模塊,運行和使用代碼,能夠幫助你徹底理解這些代碼.: 不要墜入快速的簡單修復之中.要投入時間和精力保持代碼的整潔、敞亮。 切身感受 在項目中,代碼應該是很亮堂的,不應該有黑暗死角。你也許不知道每塊代碼的每個細節,或者每個算法的每個步驟,但是你對整體的相關知識有很好的了解。沒有任何一塊代碼被警戒線或者"切勿入內"的標志隔離開. 平衡的藝術 □ 你必須要理解一塊代碼是如何工作的,但是不一定需要成為一位專家.只要你能使用它進行有效的工作就足夠了,不需要把它當作畢業生事業. □ 如果有一位團隊成員宣布,有一塊代碼其他人都很難看懂,這就意味著任何人(包括原作者)都很難維護它.請讓它變得簡單些. □ 不要急于修復一段沒能真正理解的代碼.這種+1/-1的病癥始于無形,但是很快就會讓代碼一團糟.要解決真正的問題,不要治標不治本. □ 所有的大型系統都非常復雜,因此沒有一個人能完全明白所有的代碼.除了深入了解你正在開發的那部分代碼之外,你還需要從更高的層面來了解大部分代碼的功能,這樣就可以理解系統各個功能塊之間都是如何交互的. · 如果系統的代碼已經惡化,可以閱讀第23頁習慣4中給出的建議. 對事不對人: 你在這個設計上投入了很多精力,為他付出很多心血。你堅信它比其他任何人的設計都棒。別聽他們的,他們只會把問題變得更糟糕。” 你很可能見過,對方案設計的討論失控變成了情緒化的指責 做決定是基于誰提出了這個觀點,而不是權衡觀點本身的利弊。我們曾經參與過那樣的會議,最后鬧的大家都很不愉快。 但是,這也很正常。當Lee先生在做一個新的方案介紹的時候,下面有人會說:“那樣很蠢!”(這也就暗示著Lee先生也很蠢。)如果把這句話推敲一下,也許會好一點:“那樣很蠢,你忘記考慮它要線程安全。”事實上最適合并且最有效的表達方式應該是:“謝謝,Lee先生。但是我想知道,如果兩個用戶同時登錄會發生什么情況?” 看出其中的不同了吧!下面我們來看看對一個明顯的錯誤有哪些常見的反應。 □ 否定個人能力。 □ 指出明顯的缺點,并否定其觀點。 □ 詢問你的隊友,并提出你的顧慮。 第一種方法是不可能成功的。即使Lee先生是一個十足的笨蛋,很小的問題也搞不定,但你那樣指出問題根本不會對他的水平有任何的提高,反而會導致他以后再也不會提出自己的任何想法了。第二種方法至少觀點明確,但也不能給Lee太多的幫助,甚至可能會讓你自己惹火上身。也許Lee能巧妙地回復你對非線程安全的職責:“哦,不過它不需要多線程。因為他只在Frozbot模塊的環境中使用,它已經運行在自己的線程中了。”哎喲!忘記了Frozbot這一茬了。現在該是你覺得自己蠢了,Lee也會因為你罵他笨蛋而生氣。 現在看看第三種方法。沒有譴責,沒有評判,只是簡單的表達自己的觀點。讓Lee自己意識到這個問題,而不是掃他的面子。由此可以開始一次交談,而不是爭辯。 在一個需要緊密合作的開發團隊中,如果能稍加注意禮貌對待他人,將會有益于整個團隊關注有真正價值的問題,而不是勾心斗角,誤入歧途。我們每個人都能有一些極好的創新想法,同樣也會萌生一些很蠢的想法。 如果你準備提出一個想法,卻擔心有可能被嘲笑,或者你要提出一個建議,卻當心自己丟面子,那么你就不會主動提出自己的建議了。 ,好的軟件開發作品和好的軟件設計,都需要大量的創造力和洞察力。分享并融合各種不同的想法和觀點,遠遠勝于單個想法為項目帶來的價值、 負面的評論和態度扼殺了創新。現在我們并不是提倡在設計方案的會議上手拉手唱《學習雷鋒好榜樣》,這 樣也會降低會議的效率。但是,你必須把重 點放在解決問題上,而不是去極力證明誰的 注意更好。在團隊中,一個人只是智商高是 沒用的,如果他還很頑固并且拒絕合作,那就 更糟糕。在這樣的團隊中,生產率和創新率都會冰凌滅亡的邊緣。 我們每個人都會有好的想法,也會有不對的想法,團隊中的每個人都需要自由地表達觀點。即使你的建議不被全盤接受,也能對最終解決問題有所幫助。不要害怕受到批評。記住,任何一個專家都是從這里開始的。用Les Brown的一句話說就是:“你不需要很出色才能起步,但是你必須起步才能變的很出色。” 下面是一些有效的特殊技術: 設定最終期限。如果你正在參加設計方案討論會,或者是尋找解決方案是遇到問題,清設定一個明確的最終期限,例如午飯時間或者一天的結束。這樣的時間限制可以防止人們陷入無休止的理論爭辯中,保證團隊工作的順利進行。同時(我們覺得)應現實一些:沒有最好的答案,只有更適合的方案。設定期限能夠幫你在為難的時候果斷做出決策,讓工作可以繼續進行。 逆向思維。團隊中的每個成員都應該意識到權衡的必要性。一種客觀對待問題的辦法是:先是積極地看到他的正面,然后再努力地從反面去認識他。目的是要找出優點最多缺點最少的那個方案,而這個好辦法可以盡可能地發現其優缺點。這也有助于少帶個人感情。 設立仲裁人。在會議的開始,選擇一個仲裁人作為本次會議的決策者。每個人都要有機會針對問題暢所欲言。仲裁人的責任就是確保每個人都有發言的機會,并維持會議的正常進行。仲裁人可以防止明星員工操作會議,并及時打斷假大空式發言。 如果你自己沒有積極參與這次討論活動,那么你最好退一步做會議的監督者。仲裁人應該專注于調停,而不是發表自己的觀點(理想情況不應該在整個項目中有既得利益)。當然,這項任務不需要嚴格的技術技能,需要的是和他人打交道的你能力。 支持已經做出決定。 一旦方案被確定了(不管是什么樣的方案), 每個團隊成員都必須通力合作, 努力實現這個方案。 每個人都要時刻記住, 我們的目標是讓項目成功滿足用戶需求。 客戶并不關心是誰的注意 他們關心的是,這個軟件是否可以工作,并且是否符合他們的期望。結果最重要、 實際充滿了妥協(生活本省也是如此),成功屬于意識到這一點的團隊。工作中不感情用事是需要克制力的,而你若能彰顯出成熟大度來,大家一定不會視而不見。這需要有人帶頭,身體力行,去感染另一部分人。 : 對事不對人。 讓我們驕傲的應該是解決了問題,而不是比較出誰的主意更好。 切身感受 一個團隊能夠很公正地討論一些方案的優點和缺點,你不會因為拒絕了有太多缺點的方案而傷害別人,也不會因為采納了某個不甚完美(但是更好的)解決方案而被人忌恨。 平衡的藝術 □ 經歷貢獻自己的好想法,如果你的想法沒有被采納也無需生氣。不要因為只是想體現自己的想法而對擬定的好思路畫蛇添足。 □ 脫離實際的反觀點會使爭論變味。若對一個想法有成見,你很容易提出一堆不太可能發生或者不太實際的情形去批駁它。這時,清先捫心自問:類似問題以前發生過嗎?是否經常發生? □ 也就是說,想這樣說是不夠的:我們不能采用這個方案,因為數據庫廠商可能會倒閉。或者:用戶絕對不會接受那個方案。你必須要評判那些場景發生的可能性有多大。想要支持或者反駁一個觀點,有時候你必須先做一個原型或者調查出它有多少的同意者或者反對者。 □ 只有更好,沒有最好。盡管“最佳實踐”這個術語到處在用,但實際上不存在“最佳”,只有在某個特定條件下更好的實踐、 □ 不帶個人情緒并不是要盲目地接受所有的觀點。用合適的詞和理由去解釋為什么你不贊同這個觀點或方案,并提出明確的問題。 4.排除萬難,奮勇前進 “如果你發現其他人的代碼有問題,只要你自己心里知道就行了。畢竟,你不想傷害他們,或者惹來麻煩。如果他是你的老板,更好格外謹慎,只要按照他的命令執行就可以了。” 有一則寓言叫“誰去給貓系鈴鐺”(who Will Bell the Cat)。老鼠們打算在貓的脖子上系一個鈴鐺,這樣貓巡邏靠近的時候,就能預先得到警報。每只老鼠都點頭,熱為這是一個絕妙的想法。這是一只年老的老鼠問道:“那么,誰愿意挺身而出去系鈴鐺呢?”毫無疑問,沒有一只老鼠站出來。當然,計劃也就這樣泡湯了。 有時,絕妙的計劃會因為勇氣不足而最終失敗。盡管前方很危險 不管是真魚雷或者只是一個比喻 你必須有勇氣向錢沖鋒,做你認為對的事情、 加入要你修復其他人編寫的代碼,而代碼很難理解也不好使用。你是應該繼續修復工作,保留這些臟亂的代碼呢,還是應該告訴你的老板,這些代碼太爛了,應該通通扔掉呢? 也許你會跳起來告訴周圍的人,那些代碼是多么的糟糕,但那只是抱怨和發泄,并不能解決問題。相反,你應該重寫這些代碼,并比較重寫前后的優缺點。動手證明(不要只是嚷嚷)最有效的方式,是把糟糕的代碼放到一邊,立刻重寫。列出重寫的理由,會有助于你的老板(以及同事)認清當前形勢,幫助他們得到正確的解決方案。 在假定你在處理一個特定的組件。突然,你發現完全弄錯了,你需要推翻重來。當然,你也會很擔心向團隊其他成員說明這個問題,以爭取跟多的時間和幫助。 當發現問題時, 不要試圖掩蓋這些問題。而要有勇氣站起來, 說:“我現在知道了,我過去使用的方法不對。 我想到了一些方法, 可以解決這個問題 如果你有更好的想法,我也很樂意聽一聽 但可能會花多些時間。”你已經把所有的問題的負面情緒拋諸腦后,你的意圖很清楚,就是尋找解決方案。既然你提出大家一起努力解決問題,那就你不回有任何爭辯的余地。這樣會促進大家去解決問題。也許,他們就會主動走進,提供幫助。更重要的是,這顯示出了你的真誠和勇氣,同時你也贏得了他們的信任。 你深知怎樣做才是正確的,或者至少知道目前的做法是錯誤的。要有勇氣想其他的項目成員、老板、或者客戶解釋你的不同觀點。當然,這并不容易。也許你會拖延項目的進度,冒犯項目經理,甚至惹惱投資人。但你都要不顧一切,向著正確的方向奮力前進。 美國南北戰爭時的海軍將Dabid Farragut曾經說過一句名言:“別管他媽的魚雷,Drayton上校,全速前進。”確實,前面埋伏著水雷(那時叫魚雷),但是要突破防線,只有全速前進(事實上,Farragut 的原話往往被簡化為:“別管他媽的魚雷,全速前進!”)。 切身感受 勇氣會讓人覺得有點不自在,提前鼓足勇氣更需要魄力。但有些時候,它是掃除障礙的唯一途徑,否則問題就會進一步惡化下去。鼓起你的勇氣,這能讓你從恐懼中解脫出來。 平衡的藝術 · 如果你說天快要塌下來了,但其他團隊成員都并不贊同。反思一下,也許你是正確的,但你沒有解釋清楚自己的理由。 · 如果你說天快要塌下來了但其他團隊成員都不贊同。認真考慮一下,他們也許是對的。 · 如果設計或代碼中出現了奇怪的問題,花時間去理解為代碼會是這樣的。如果你找到了解決的辦法,但代碼仍然另人費解,唯一的解決辦法就是重構代碼,讓他可讀性更強。如果你沒有馬上理解那段代碼,不要輕易的否定和重寫他們。那不是勇氣,而是魯莽。 · 當你勇敢站出來提問時,如果收到了缺乏背景知識的抉擇者的抵制,你需要用他們能夠聽懂的話語表達。“更清晰的代碼”是無法打動生意人的。節約資金、獲取更好的投資回報,避免訴訟以及增加用戶利益,會讓論點更具說服力。 · 如果你在壓力下要對代碼質量做出妥協,你可以指出,作為一名開發者,你沒有職權毀壞公司的資產(所有的代碼)。 □ 學無止境 即使你已經在正確的軌道上,但如果只是停止不前,也仍然會被淘汰出局。Will Rogers (美國著名演員) 敏捷需要持續不斷的學習和充電。正如上面引用的Will Rogers的話,逆水行舟,不進則退。那不僅是賽馬場上的真理,它更適合我們當今的程序員。 軟件開發行業是一個不停發展和永遠變化的領域。雖然有一些概念一直有用,但還有很多只是很快就會過時。從事軟件開發行業就像是在跑步機上,你必須一直跟上步伐穩步前進,狗則就會摔倒出局。 誰會幫助你保持步伐前進呢?在一個企業化的社會中,只有一個人會為你負責------那就是你自己。是否能跟上變化,完全取決于你自己。 許多新技術都基于現有的技術和思想。他們會加入一些新的東西,這些新東西是逐步加入的量,如果你跟蹤技術變化,那么學習這些新東西對你來說就是了解這些增量變化。如果你不跟蹤變化,技術變化就會顯得很突然并且難以應付。這就好比少小離家老大回,你會發現變化很大,甚至有很多地方都不認識。然而,居住在那里的人們,每天只看到小小的變化,所以非常適應。在第28頁我們會介紹一些跟蹤變化的方法。 給自己投資,讓自己與時俱進,當然在好不過,但是也要努力對團隊投資,這個目標怎么實現呢?你將從第31頁學到實現這個目標的一些方法。 學習新技術和新的開發方法很重要,同時你也要能摒棄陳舊和過時的開發方法。換句話說,你需要懂得丟棄(請閱讀第34頁) 當我們談到變化這個話題的時候,要認識到你對問題的理解在整個項目期間也是在變化的。你曾經認為自己已經很明白的事情,現在也許并不是你想象中的那樣。你要對沒有去年全理解的某些疑問不懈的深入追蹤下去,我們將從第37頁開始講述為什么要打破沙鍋問到底,以及如何有效地提問。 最后,一個活力十足的冥界開發團隊需要有規律反復地做很多事情,一旦項目開始運作你就要把握開發節奏,我們會在第40頁介紹這種節奏感 5.跟蹤變化軟件技術的變化如此之快,勢不可擋,這是它的本性。繼續用你熟悉的語言做你的老本行吧,你不可能跟上技術變化的腳步。 赫拉克利說過:“唯有變化是永恒的。”歷史已經證明了這句真理,在當今快速發展的IT時代尤其如此。 你從事的是一項充滿激情且不停變化的工作。如果你畢業于計算機相關的專業,并覺得自己已經學完了所有知識,那你就大錯特錯了。假設你是10多年前的1995年畢業的, 那時,你掌握了哪些技術呢? 可能你的C++還學得不錯, 你了解有一門新的語言交java,一種被稱作是設計模式的思想開始引起大家的關注。一些人會談論被稱作因特網的東東。如果那個時候你就不再學習,而在2005年的時候重出江湖。再看看周圍,就會發現變化巨大,就算是在一個相當狹小的技術領域,要學習那些新技術并達到熟練的程度,一年的時候也不夠。 技術發展的步伐如此快速,簡直讓人難以置信。就以java為例,你掌握了java語言及其一系列的最新特性。接著你要掌握Swing,JSP,Struts,Tapestry,jsf,JDBC,JDO,Hibernate,JMS,EJB,Lucene,Spring ….還可以列舉很多,如果你使用的是微軟的技術,要掌握VB, VisualC++, MFC, COM, ATL, .NET,C#, VB.NET , ASP..NET,ADO.NET, WinForm, Enterprise, Service,Biztalk,….并且不要忘記還有UML, Ruby,XML,DOM,SAX,JAXP,JDOM,XSL,Schema,SOAP,Web Service, SOA,同樣還可以繼續列舉下去(我們將會用光所有的縮寫字母)。不幸的是,如果只是掌握了工作中需要的技術并不夠。那樣的工作也許幾年之后就不再有了----它會被外包或者會過時,那么你也就會出局。假設你是Visual C++或者VB程序員,看到Com技術出現了,你花時間去學習它(雖然很痛苦),并且隨時了解分布式對象計算的一切。當xml出現的時候,你花時間去學習它。你深入研究ASP,熟知如何用它來開發WEB應用。你雖然不是這些技術的專家,但也不是對它們一無所知。好奇心促使你去了解MVC是什么。 如果你跟上了這些新技術,接下來學習.NET就不再是大問題。你不需要一口氣爬上10樓,而需要一直在攀登,所以最后看起來就像只要再上一二層,如果你對所有這些技術都一無所知,想要馬上登上十樓, 肯定會讓你喘不過氣來。而且,這也會花很長時間,期間還會有更新的技術出現。如何才能跟上技術變化的步伐呢?幸好,現今有很多方法和工具可以幫助我們繼續充電。下面是一些建議。 迭代和增量式學習。每天計劃用一段時間來學習新技術,它不需要很長時間,但需要經常進行。記下哪些你想學習的東西-------當你聽到一些不熟悉的術語或者短語時,簡要地把它記錄下來。然后再計劃的時間中深入研究它。 了解最新行情。互聯網上有大量關于學習新技術的資源。閱讀社區討論的和郵件列表,可以了解其他人遇到的問題,以及他們發現的很酷的解決方案。額一些公認的優秀技術博客,經常去讀一讀,以了解那些頂尖的博客作者們在關注什么(最新的博客列表請參照pragmaticprogrammer.com)。 參加本地的用戶組活動。Java,ruby,delphi,.NET,過程改進,面向對象設計,Linux,Mac,以及其他的各種技術在很多地區都會有用戶組,聽講座,然后積極加入到問答環節中。 參加研討會議。計算機大會在世界各地舉行,許多知名的顧問或者作者支持研討會或者課程。這些聚會時向專家學習的最直接的好機會。 如饑似渴的閱讀。找一些關于軟件開發和非技術主題的好書(我們很樂意為你推薦),也可以是一些專業的期刊和商業雜志,甚至是一些大眾媒體新聞(有趣的是在那些常常能看到老技術被吹捧為最新潮流)。 切身感受你能嗅到將要流行的新技術,知道它們已經發布或投入使用。如果必須要把工作切換到一種新的技術領域,你能做到。 平衡的技術· 許多新想法從末變得羽翼豐滿,成為有用的技術。即使是大型,熱門和資金充裕的項目,也會有同樣的下場。你要正確把握資金投入的精力。 · 你不可能精通每一項技術,沒有必要去做這樣的嘗試。只要你在某些方面成為專家,就能使用同樣的方法,很容易地成為新領域的專家。 · 你要明白為什么需要這項新技術--------它視圖解決什么樣的問題?它可以被用在什么地方? · 避免在一時沖動的情況下,只是因為想學習而將應用切換到新的技術,框架或者開發語言。在做決策之前,你必須評估新技術的優勢。開發一個小的原型系統,是對付技術狂熱者的一劑良藥。 6.對團隊投資 “不要和別人分享你的知識--------自己留著。你是因為這些知識而成為團隊中的佼佼者,只要自己聰明就可以了,不用管其他失敗者。“團隊中的開發者們各有不同的能力、經驗和技術。每個人都各有所長。不同才能和背景的人混在一起,是一個非常理想的學習環境。在一個團隊中,如果只是你個人技術很好還遠遠不夠。如果其他團隊成員的知識不夠,團隊也無法發揮其應有的作用:一個學習型的團隊才是較好的團隊。當開發項目的時候,你需要使用一些術語或者隱喻來清晰地傳達設計的概念和意圖。如果團隊中的大部分成員不熟悉這些,就很難進行高效地工作。再比如你參加了一個課程或者研討班之后,所學的知識如果不用,往往就會忘記。所以,你需要和其他團隊成員分享所學的知識,把這些知識引入團隊中。找出你或團隊中的高手擅長的領域,幫助其他的團隊成員在這些方面迎頭趕上(這樣做還有一個好處是,可以討論如何將這些東西應用于自己的項目中)。“午餐會議”是在團隊中分享知識非常好的方式。在一周之中挑選一天,例如星期三(一般來說任何一天都可以,但最好不要是星期一和星期五)。事先計劃午餐時聚集在一起,這樣就不會擔心和其他會議沖突,也不需要特別的申請。為了降低成本,就讓大家自帶午餐。每周,要求團隊中的一個人主持講座。他會給大家介紹一些概念,演示工具,或者做團隊感興趣的任何一件事情。你可以挑一本書,給大家說說其中一些特別內容、項目或者實踐。①無論什么主題都可以。從每周主持講座的人開始,先讓他講15分鐘,然后,進行開放式討論,這樣每個人都可以發表自己的意見,討論這個主題對于項目的意義。討論應該包括所能帶來的益處,提供來自自己應用程序的示例,并準備好聽取進一步的信息。這些午餐會議非常有用。它促進了整個團隊對這個行業的了解,你自己也可以從其他人身上學到很多東西。優秀的管理者會重用那些能提高其他團隊成員價值的人,因此這些活動也直接有助于你的職業生涯。 提供你和團隊學習的更好平臺。通過午餐會議可以增進每個人的知識和技能,并幫助大家聚集在一起進行溝通交流。喚起人們對技術和技巧的激情,將會對項目大有裨益。 切身感受這樣做,會讓每個人都覺得自己越來越聰明。整個團隊都要了解新技術,并指出如何使用它,或者指出需要注意的缺陷。 平衡的藝術 □ 讀書小組逐章一起閱讀一本書,會非常有用,但是要選好書。《7天用設計模式和UML精通…》也許不會是一本好書。 □ 不是所有的講座都能引人入勝,有些甚至顯得很不合時宜。不管怎么樣,都要未雨綢繆;諾亞在建造方舟的時候,可并沒有開始下雨,誰能料到后來洪水泛濫呢? □ 盡量讓講座走入團隊中。如果午餐會議在禮堂中進行,有餐飲公司供飯,還要使用幻燈片,那么就會減少大家接觸和討論的機會。 □ 堅持有計劃有規律地舉行講座。持續、小步前進才是敏捷。稀少、間隔時間長的馬拉松式會議非敏捷也。 □ 如果一些團隊成員因為吃午飯而缺席,用美食引誘他們。 □ 不要局限于純技術的圖書和主題,相關的非技術主題(項目估算、溝通技巧等)也會對團隊有幫助。 □ 午餐會議不是設計會議。總之,你贏專注討論那些與應用相關的一般主題。具體的設計問題,最好是留到設計會議中去解決。 7.懂得丟失 “那就是你一貫的工作方法,并且是有原因的,這個方法也很好地為你所用。開始你就掌握了這個方法,很明顯它是最好的方法,真的,從那以后就不要再改變了。” 敏捷的根本之一就是擁抱變化。既然變化時永恒的,你有可能一直相同的技術和工具嗎? 不,不可能。我們一直在本章說要學習新技術和進方法。但是記住,你也需要學會如何丟棄。 隨著科技進步,曾經非常有用的東西往往會靠邊站。它們不再有用了,它們還會降低你的效率。當Andy第一次編程的時候,內存占用時一個大問題。你通常無法再主存儲器(大約48KB)中一次裝載整個程序,所以必須把程序切分成塊。當一個程序塊交換進去的時候,其他一些程序必須出來,并且你無法再一個塊中調用另一個塊中的函數。 正是這個實際約束,極大地影響了你的設計和編程技術。 想想在過去,面對處理器之外的循環操作,你必須花費很大的精力去手動調整匯編語言的編譯輸出。可以想象,如是是使用JavaScript或者J2EE代碼,你還需要這么干嗎? 對于大多數的商業應用,技術已經有了巨大的變化,不再像以前那樣,處處考慮內存占用、手動的重復占位及手工調整匯編語言。但我們仍然看到很多開發者從未丟棄這些習慣。 Andy曾經看到過這樣一段C語言代碼:一個大的for循環,循環里面的代碼一共輸出了60頁。那個作者“不相信”編譯器的優化,所以決定自己手動實現循環體展開和其他一些技巧。我們只能祝愿維護那一大堆代碼的人好運。 在過去,這段的挨罵也許可以勉強接受。但是,現在絕對不可以了。電腦和 CPU曾經非常昂貴,而現在它們就是日用品。現在,開發者的時間才是緊缺和昂貴的資源。 這樣的轉變在緩慢地進行著,但是人們也真正認清了這個事實。我們看到,需要耗費10人年開發的J2EE項目已經從輝煌走向下坡路。使用PHP,一個月的時間就可以完成,并能交付大部分的功能。想PHP這樣的語言或Ruby on Rails 這樣的框架越來越受關注(參見[TH05]),這表明了開發者已經意識到舊的技術再也行不通了。 但丟棄已經會的東西并不容易。很多團隊在猶豫,是因為管理者拒絕用500美元購買一臺構建愛你機器(build machine),卻寧愿花費好幾萬美元的人工費,讓程序員花時間找出問題。而實際上,買一臺構建機器就可以解決這些問題。如果購買機器需要花費500 000美元,那樣做還情有可原,但現在早已時過境遷了。 在學習一門新技術的時候,多問問自己,是否把太多舊的態度和方法用在了新技術上。學習面向對象編程和學習面向對象過程是截然不同的。很容易會發現有人用C語言的方式編寫Java代碼,用VB的方式編寫C#的代碼(或者用Fortran的方式做任何事情)。這樣,你辛苦地轉向一門新的語言,卻失去了期望獲得的益處。 打破舊習慣很難,更難的是自己還沒有意識到這個問題。丟棄的第一步,就是要意識到你還在使用過時的方法,這也是很難的部分。另一個難點就是要做到真正地丟棄舊習慣。思維定式是經過多年摸爬滾打才構建成型的,已經根深蒂固,沒有人可以很容易就丟棄它們。 這也不是說你真的要完全丟棄它們。前面那個內存重復占位的例子,只是在稍大緩存中用手工維護一組工件的特殊案例。盡管實現方式不同了,但以前的技術還在你的大腦中。你不可能撬開大腦,把這一段極易神經剪掉。其實,根據具體情況還可以運用舊知識。如果環境合適,可以舉一反三地靈活應用,但一定要保證不是習慣性地落入舊習慣。 應該力求盡可能完全轉入新的開發環境。例如,學習一門新的編程語言時,應使用推薦的集成開發環境,而不是你過去開發時用的工具插件。用這個工具編寫一個和過去完全不同類型的項目。轉換的時候,完全不要使用過去的語言開發工具。只有更少被舊習慣牽絆,才更容易養成新習慣。 學習新的東西,丟棄舊的東西。在學習一門新技術的時候,要丟棄會阻止你前進的舊習慣。畢竟,汽車要比馬車車廂強得多。 切身感受 新技術會讓人感到有一點恐懼。你確實需要學習很多東西。已有的技能和習慣為你打下很好的基礎,但不能依賴它們。 平衡的藝術 □沉舟側畔千帆過,病樹前頭萬木春。要果斷丟棄舊習慣,一味遵循過時的舊習慣會危害你的職業 生涯。 □不是完全忘記舊的習慣,而是只在使用適當的技術時才使用它。 □對于所使用的語言,要總結熟悉的語言特性,并且比較這些特性在新語言或新版本中有什么 不同。 8.打破砂鍋問道底 “接受別人給的解釋。別人告訴你問題出在了什么地方,你就去看什么地方。不需要再浪費時間去追根究底。” 前面談到的一些習慣是關于如何提高你和團隊的技術的。下面有一個習慣幾乎總是有用的,可以用于設計、調試以及理解需求。 假設,應用系統出了大問題,它們找你來修復它。但你不熟悉這個應用系統,所以它們會幫助你,告訴你問題一定是出在那個特殊的模塊中——你可以放心地忽略應用系統的其他地方。你必須很快地解決這個問題,因為跟你合作的這些人耐心也很有限。 當你受到那些壓力的時候,也許會覺得受到了脅迫,不想去深入了解問題,而且別人告訴你的已經深入了。然而,為了解決問題,你需要很好地了解系統的全局。你需要產看所有你認為霍問題相關的部分——即使他人覺得這并不相干。 觀察一下醫生是如何工作的,當你不舒服的時候,醫生會問你各種各樣的問題——你有什么習慣,你吃了什么東西,什么地方疼痛,你已經服過什么藥等。人的身體非常復雜,會受到很多因素的影響。如果醫生沒有全面地了解狀況,就很可能出現誤診。 例如,住在紐約的一個病人患有高燒、皮疹、嚴重的頭痛、眼睛后面疼痛,以及肌肉或關節疼痛,它也許是染上了流感或者麻疹。但是,通過全面的檢查,醫生發現這個倒霉的病人剛去南美洲度假回來。所以,這病也許并不是簡單的流感,還有可能是在新大陸染上的熱帶傳染病登革熱。 在計算機世界中葉很相似,很多問題都會影響你的應用系統。為了解決問題,你需要知道許多可能的影響因素。當找人詢問任何相關的問題時,讓他們耐心地回答你的問題,這是你的職責。 或者,假設你和資深的開發者一起工作。他們有可能比你更了解這個系統。但他們也是人,有時他們也會忘記一些東西。你的問題甚至會幫助探明理清思路。你從一個新人角度提出的問題,給他們提供了一個新的視角,也許就幫助他們解決了一直令人困擾的問題。 “為什么”是一個非常好的問題,事實上,在一本流行的管理圖書《第五項修煉》中,作者建議,在理解一個問題的時候,需要漸次地問5個以上的“為什么”。這聽起來就像退回到了4歲,那時對一切都充滿著好奇。它是很好的方式,進一步挖掘簡單直白的答案,通過這個路線,設想就會更加接近事實真相。 在《第五項修煉》一書中就有這樣的例子。咨詢師訪問一個制造設備工廠的經理,就用到了這樣一個追根究底的分析。看到地板上有油漬的時候,經理的第一反應是命令工人把它打掃干凈。但是,咨詢師問:“為什么地板上有油漬?”經理不熟悉整個流程,就會責備這是清潔隊的疏忽。咨詢師再問:“為什么地板上有油漬?”通過一系列漸次提出的“為什么”和許多不同部門員工的幫助,咨詢師最后找到了真正的問題所在“采購政策表述不明確,導致大量采購了一批有缺陷的墊圈。 答案出來之后,經理和其他員工都十分震驚,他們對這事一無所知。由此發現了一個重大的隱患,避免了其他方面更大的損失。而咨詢師所做的不過就是問了“為什么“。 “哎呀,只要每周重啟一次系統,就沒有問題了。”真的嗎?為什么呀?“你必須依次執行3次構建才能完成構建。” 真的嗎?為什么呀?“我們的用戶根本不想要那個功能” 真的嗎?為什么呀? 為什么呀? 不停的問為什么。不能只滿足于別人告訴你的表面現象,要不停地提問知道你明白問題的根源。 切身感受 這就好比從礦石中采掘貴重的珠寶。你不停地篩選掉無關的物質,一次比一次深入,直到找到發光的寶石。你要能感覺到真正地理解問題,而不是只知道表面的癥狀。 平衡的藝術 □你可能會跑題,問了一些與主題無關的問題。就好比是,如果汽車啟動了,你問是不是輪胎出了問題,就是沒有任何幫助的。問“為什么”,但是要問道點子上。 □當你問“為什么”的時候,也許你會被反問:“為什么你問這個問題?”在提問之前,想好你提問的理由,這會有助于你問出恰當的問題。 □“這個,我不知道”是一個好的起點,應該由此進行更進一步的調查。而不是在此戛然結束。 9.把握開發節奏 “我們很長時間沒有進行代碼復審,所以這周會復審所有的代碼。此外,我們也要做一個發布計劃了,那就從星期二開始,用3周時間做下一個發布計劃。” 在許多不成功的項目中,基本上都是隨意安排工作計劃,沒有任何的規律。那樣的隨機安排很難處理。你根本不知道明天將會發生什么,也不知道什么時候開始下一輪的全休“消防演習”。 但是,敏捷項目會有一個節奏和循環,讓開發更加輕松。例如,Scrum約定了30天之內不應該發生需求變化,這樣確保團隊有一個良性的開發節奏。這有助于防止一次計劃太多的工作和一些過大的需求變更。 相反,很多敏捷實踐必須一直進行,也就是說,它貫穿于項目的整個生命周期。有人說,上帝發明了時間,就是為了防止所有事情同時發生。因此我們需要更具遠見,保持不同的開發節奏,這樣敏捷項目的所有事情就不會突然同時發生,也不會隨機發生,時間也不會不可預知。 我們先來看某個工作日的情況。你希望每天工作結束的時候,都能完成自己的工作,你手上沒有遺留下任何重要的任務。當然,每天都能這樣是不現實的。但是,你可以做到在每天下班離開公司前運行測試,并提交一天完成的代碼。如果已經很晚了,并且你只是嘗試性地編寫了一些代碼,那么也許最好應該刪掉這些代碼,第二天從頭開始。 這個建議聽起來十分極端,也許確實有一點。①但是如果你正在開發小塊的任務,這個方式非常有助于你管理自己的時間:如果在你工作的時候沒有一個固定的最終期限(例如一天的結束),就應該好好想想了。它會讓你的工作有一個節奏,在每天下班的時候,提交所有的工作,開心收工。這樣,明天就能開始新的內容,解決下一系列難題。 站立會議(習慣38,第148頁)最好每天在固定的時間和地點舉行,比如說上午10點左右。要養成這樣的習慣,在那時就準備好一切參加站立會議。 最大的節拍就是迭代時間(習慣17,第69頁),一般是1~4周的時間。不管你的一個迭代是多長,都應該堅持-----確保每個迭代周期的時間相同很重要。運用有規律的開發節奏,會更容易達到目標,并確保項目不停地前進。 解決 ,在事情變得一團糟之前。保持事件之間穩定重復的間隔,更容易解決常見的重復任務。 切身感受項目開發需要有一致和穩定的節奏。編輯,運行測試,代碼復審,一致的迭代,然后發布。如果知道什么時候開始下一個節拍,跳舞就會更加容易。 平衡的藝術 · 在每天結束的時候,測試代碼,提交代碼,沒有殘留的代碼。 · 不要搞得經常加班。 · 已固定、有規律的長度運行迭代(第69頁,習慣17)。也許剛開始你要調整迭代的長度,找到團隊最舒服可行的時間值,但之后就必須要堅持。 · 如果開發節奏過于密集,你會精疲力竭的。一般來說,當與其他團隊(或組織)合作時,你需要減慢開發節奏。因此人們常說,互聯網時代發展太快,有害健康。 · 有規律的開發節奏會暴露很多問題,讓你有更多鼓起勇氣的借口(第23頁,習慣4)。 · 就像是減肥一樣,一點點的成功也是一個很大的激勵。小而可達到的目標會讓每個人全速前進。慶祝每一次難忘的成功:共享美食和啤酒或者團隊聚餐。 第四章 交付用戶想要的軟件 沒有任何計劃在遇敵后還能繼續執行。---------- Helmuth von Moltke(德國陸軍元帥,18481916) 客戶把需求交給你了,要你幾年后交付這個系統。然后,你就基于這些需求構建客戶需要的系統,最后按時交付。客戶看到了軟件,連聲稱贊做得好。從此你又多了一個忠實客戶,接著你很開心地進入了下一個項目。你的項目通常都是這樣運作的,是這樣的嗎? 其實,大部分人并不會遇到這樣的項目。通常情況是:客戶最后看到了軟件,要么震驚要么不高興。他們不喜歡所看到的軟件,他們認為很多地方需要修改。他們要的功能不在他們給你的原始需求文檔中。這聽起來是不是更具有代表性? Helmuth von Moltke曾說過:“沒有任何計劃在遇敵后還能繼續執行。”我們的敵人不是客戶,不是用戶,不是隊友,也不是管理者。真正的敵人是變化。軟件開發如戰爭,形勢的變化快速而又劇烈。固守昨天的計劃而無視環境的變化會帶來災難。你不可能“戰勝”變化—無論它是設計、架構還是你對需求的理解。敏捷—成功的軟件開發方法—取決于你識別和適應變化的能力。只有這樣才有可能在預算之內及時完成開發,創建真正符合用戶需求的系統。 在本章中,我們會介紹如何達到敏捷的目標。首先,要介紹為什么用戶和客戶參與開發如此重要,以及為什么讓客戶做決定(從第45頁開始)。設計是軟件開發的基礎,沒有它很難做好開發,但你也不能被它牽制。從第48頁開始,將介紹如何讓設計指導而不是操縱開發。說到牽制,你應確保在項目中引入合適的技術。你需要合理地使用技術(第52頁介紹)。 為了讓軟件符合用戶的需求,要一直做下面的準備工作。為了降低集成新代碼帶來的破壞性變化,你要提早集成,頻繁集成(第58頁)。當然,你不想破壞已有的代碼,想讓代碼一直保持可以發布(從第55頁開始)。 你不能一次又一次為用戶演示新功能,而浪費寶貴的開發時間,因此你需要提早實現自動化部署(第61頁)。只要你的代碼一直可用,并且易于向用戶部署,你就能使用演示獲得頻繁反饋(第64頁)。這樣你就能經常向全世界發布新版本。你想通過使用短迭代,增量發布來幫助經常發布新功能,與用戶的需求變化聯系更緊密(從第69頁開始介紹它)。 最后,特別是客戶要求預先簽訂固定價格合約時,很難通過敏捷的方法讓客戶與我們同坐一條船上。而且,事實上是固定的價格就意味著背叛承諾,我們會在第73頁了解如何處理這種情況。 10.讓客戶做決定 “開發者兼具創新和智慧,最了解應用程序。因此,所有關鍵決定都應該由開發者定奪。每次業務人員介入的時候,都會弄得一團槽,他們無法理解我們做事的邏輯。” 在設計方面,做決定的時候必須有開發者參與。可是,在一個項目中,他們不應該做所有的決定,特別是業務方面的決定。 就拿項目經理Pat的例子來說吧。Pat的項目是遠程開發,一切按計劃且在預算內進行著—就像是個可以寫入教科書的明星項目。Pat高高興興地把代碼帶到客戶那里,給客戶演示,卻敗興而歸。 原來,Pat的業務分析師沒有和用戶討論,而是自作主張,決定了所有的問題。在整個開發過程中,企業主根本沒有參與低級別的決策。項目離完成還早著呢,就已經不能滿足用戶的需要了。這個項目一定會延期,又成為一個經典的失敗案例。 因而,你只有一個選擇:要么現在就讓用戶做決定,要么現在就開始開發,遲些讓用戶決定,不過要付出較高的成本。如果你在開發階段回避這些問題,就增加了風險,但是你要能越早解決這些問題,就越有可能避免繁重的重新設計和編碼。甚至在接近項目最終期限的時候,也能避免與日俱增的時間壓力。 例如,假設你要完成一個任務,有兩種實現方式。第一種方式的實現比較快,但是對用戶有一點限制。第二種方式實現起來需要更多的時間,但是可以提供更大的靈活性。很顯然,你有時間的壓力(什么項目沒有時間壓力呢),那么你就用第一種很快的方式嗎?你憑什么做出這樣的決定呢?是投硬幣嗎?你詢問了同事或者你的項目經理嗎? 作者之一Venkat最近的一個項目就遇到了類似的問題。項目經理為了節約時間,采取了第一種方式。也許你會猜到,在Beta版測試的時候,軟件暴露出的局限讓用戶震驚,甚至憤怒。結果還得重做,花費了團隊更多的金錢、時間和精力。 開發者(及項目經理)能做的一個最重要的決定就是: 判斷哪些是自己決定不了的,應該讓企業主做決定。你 不需要自己給業務上的關鍵問題做決定。畢竟,那不是 你的事情,如果遇到 了一個問題,會影響到系統的行為 或者如何使用系統,把這個問題告訴業務負責人。如果項目領導或經理試圖全權負責這些問題,要委婉地勸說他們,這些問題最好還是和真正的業務負責人或者客戶商議(見習慣4,第23頁)。 當你和客戶討論問題的時候,準備好幾種可選擇的方案。不是從技術的角度,而是從業務的角度,介紹每種方案的優缺點,以及潛在的成本和利益。和他們討論每個選擇對時間和預算的影響,以及如何權衡。無論他們作出了什么決定,他們必須接受它,所以最好讓他們了解一切之后再做這些決定。如果事后他們又想要其他的東西,可以公正地就成本和時間重新談判。 畢竟,這是他們的決定。 讓你的客戶做決定。開發者、經理或者業務分析師不應該做業務方面的決定。用業務負責人能夠理解的言語,向他們詳細解釋遇到的問題,并讓他們做決定。 切身感受業務應用需要開發者和業務負責人互相配合來開發。這種配合的感覺就應該像一種良好的、誠實的工作關系。 平衡的藝術 第3章 記錄客戶做出的決定,并注明原因。好記性不如爛筆頭。可以使用工程師的工作日記或日志、Wiki、郵件記錄或者問題跟蹤數據庫。但是也要注意,你選擇的記錄方法不能太笨重或者太繁瑣。 第4章 不要用低級別和沒有價值的問題打擾繁忙的業務人員。如果問題對他們的業務沒有影響,就應該是沒有價值的。 第5章 不要隨意假設低級別的問題不會影響他們的業務。如果能影響他們的業務,就是有價值的問題。 第6章 如果業務負責人回答“我不知道”,這也是一個稱心如意的答案。也許是他們還沒有想到那么遠,也許是他們只有看到運行的實物才能評估出結果。盡你所能為他們提供建議,實現代碼的時候也要考慮可能出現的變化。 11.讓設計指導而不是操縱開發 “設計文檔應該盡可能詳細,這樣,低級的代碼工人只要敲入代碼就可以了。在高層方面,詳細描述對象的關聯關系;在低層方面,詳細描述對象之間的交互。其中一定要包括方法的實現信息和參數的注釋。也不要忘記給出類里面的所有字段。編寫代碼的時候,無論你發現了什么,絕不能偏離了設計文檔。“ “設計“是軟件開發過程不可缺少的步驟。它幫助你理解系統的細節,理解部件和子系統之間的關系,并且指導你的實現。一些成熟的方法論很強調設計,例如,統一過程(Unified Process,UP)十分重視和產品相關的文檔。項目管理者和企業主常常為開發細節困擾,他們希望在開始編碼之前,先有完整的設計和文檔。畢竟,那也是你如何管理橋梁或建筑項目的,難道不是嗎? 另一方面,敏捷方法建議你早在開發初期就開始編碼。是否那就意味著沒有設計呢?不,絕對不是,好的設計仍然十分重要。畫關鍵工作圖(例如,用UML)是不比可少的,因為,要使用類極其交互關系來描繪系統是如何組織的。在做設計的時候,你需要花時間去思考(討論)各種不同選擇的缺陷和益處,以及如何做權衡。 然后,下一步才考慮是否需要開始編碼。如果你在前期沒有考慮清楚這些問題,就草草地開始編碼,很可能會被很多意料之處的問題搞暈。甚至在建筑工程方面也有類似的情況。在鋸一根木頭的時候,通常的做法就是先鋸一塊必須要稍微長一點的木塊,最后細致地修整,直到它正好符合需求。 但是,即使之前已經提交了設計文檔,也還會有一些意料之外的情況出現。時刻謹記,此階段提出的設計只是基于你目前對需求的理解而已。一旦開始了編碼,一切都會改變。設計及其代碼實現會不停地發展和變化。 一些項目領導和經理認為設計應該盡可能地詳細,這樣就可以簡單地交付給“代碼工人們”。他們認為代碼工人不需要做任何決定,只要簡單地把設計轉化成代碼就可以了。就作者本人而言,沒有一個愿意在這樣的團隊中做純粹的打字員。我們猜想你也不愿意。 如果設計師門把自己的想法繪制成 精美的文檔,然后 把它們扔給程序員 去編碼,那會發生什么(查閱習慣39, 在第152頁)?程序員會在壓力下, 完全按照設計或者圖畫的樣子編碼。如果系統和已有代碼的現狀表明接收到的設計不夠理想,那該怎么辦?在糟糕了!時間已經花費在設計上,沒有功夫回頭重新設計了。團隊會死撐下去,用代碼實現了明明知道是錯誤的設計。這聽起來是不是很愚蠢?是夠愚蠢的,但是有一些公司真的就是這樣做的。 嚴格的需求-設計-代碼-測試開發流程源于理想化的瀑布式開發方法,它導致在前面進行了過度的設計。這樣在項目的生命周期中,更新和維護這些詳細的設計文檔變成了主要工作,需要時間和資源方面的巨大投資,卻只是有很少的回報。我們本可以做得更好。 設計可以分為兩層:戰略和戰術,前期的設計屬于戰略,通常只有在沒有深入理解需求的時候需要這樣的設計。更確切地說,它應該只描述總體戰略,不應深入到具體的細節。 前面剛說過,戰略級別的設計不應該具體說明程序方法、參數、字段和對象交互精確順序的細節。那應該留到戰術設計階段,它應該在項目開發的時候再具體展開。 良好戰略設計應該扮演地圖的角色, 指引你向正確的方向前進。任何設計 僅是一個起跑點:它就像你的代碼一 樣,在項目的生命周期中,會不停地進一步發展和提煉。 下面的故事會給我們一些啟發。在1804年,Lewis與Clark進行了橫穿美國的壯舉,他們的“設計“就是穿越蠻荒。但是,他們不知道在穿越殖民地時會遇到什么樣的問題。他們只知道自己的目標和制約條件,但是不知道旅途的細節。 軟件項目中的設計也與此類似。在沒有穿越殖民地的時候,你不可能知道會出現什么情況。所以,不要事先浪費時間規劃如何徒步穿越河流,只有當你走到河岸邊的時候,才能真正評估和規劃如何穿越。只有到那時,你才開始真正的戰術設計。 不要一開始就進行戰術設計,它的重點是集中在單個的方法或數據類型上。這時,更適合討論如何設計類的職責。因為這仍然是一個高層次、面向目標的設計,事實上,CRC(類-職責-協作)卡片的設計方法就是用來做這個事情的,每個類按照下面的術語描述。 □ 類名。 □ 職責:它應該做什么? □ 協作者:要完成工作它要與其他什么對象一起工作? 如何知道一個設計是好的設計,或者正合適?代碼很自然地為設計的好壞提供了最好的反饋。如果需求有了小的變化,它仍然容易去實現,那么它就是好的設計。而如果小的需求變化就帶來一大批基礎代碼的破壞,那么設計就需要改進。 好設計是一張地圖,它也會進化。設計指引你向正確的方向前進,它不是殖民地,它不應該標識具體的路線。你不要被設計(或者設計師)操縱。 切身感受好的設計應該是正確的,而不是精確的。也就是說,它描述的一切必須是正確的,不應該涉及不確定或者可能會發生變化的細節。它是目標,不是具體的處方。 平衡的藝術 n “不要在前期做大量的設計”并不是說不要設計。只是說在沒有經過真正的代碼驗證之前,不要陷入太多的設計任務。當對設計一無所知的時候,投入編碼也是意見危險的事。如果深入編碼只是為了學習或創造原型,只要你隨后能把這些代碼扔掉,那也是一個不錯的辦法。 n 即使初始的設計到后面不再管用,你仍需設計:設計行為是無價的。正如美國總統艾森豪威爾所說:“計劃是沒有價值的,但計劃的過程是必不可少的。”在設計過程中學習是價值的,但設計本身也許沒有太大的用處。 n 白板、草圖、便利貼都是非常好的設計工具。復雜的建模工具只會讓你分散精力,而不是啟發你的工作。 12.合理地使用技術 “你開始了一個新的項目,在你面前有一個長串關于新技術和應用框架的列表。這些都是好東西,你真的需要使用列表中所有的技術。想一想,你的簡歷上將留下漂亮的一筆,用那些偉大的框架,你的新應用將具有極高技術含量。” 從前,作者之一Venkat的同事Lisa向 他解釋自己的提議:她打算使用EJB。 Venkat表示對EJB有些顧慮,覺得它 不適合那個特殊的項目。然后Lisa回答 道:“我已經說服了我們經理,這是正確的技術路線,所以現在不要用扔‘炸彈’了。”這是一個典型的“簡歷驅動設計”的例子,之所以選擇這個技術,是因為它很美,也許還能提高程序員的技能。但是,盲目的為項目選擇技術框架,就好比是為了節省稅款而生孩子,這是沒有道理的。 在考慮引入新技術或框架之前,先要把你需要解決的問題找出來。你的表達方式不同,會讓結果有很大差異。如果你說“我們需要xyzzy技術,是因為。。。”,那么就不太靠譜。你應該這樣說:“。。。太難了”或者是“。。。花的時間太長了”,或者類似的句子。找到了需要解決的問題,接下來就要考慮: □ 這個技術框架真能解決這個問題嗎?是的,或許這是顯而易見的。但是,這個技術真能解決你面臨的那個問題嗎?或者,更尖銳一點說,你是如何評估這個技術的?是通過市場宣傳還是道聽途說?要確保它能解決你的問題,并沒有如何的毒副作業。如果需要,先做一個小的原型。 □ 你將會被它拴住嗎?一些技術是賊船,一旦你使用了它,就會被它套牢,再也不能回頭了。它缺乏可取消性(查閱【HT00】),當條件發生變化時,這可能對項目有致命打擊。我們要考慮它是開放技術還是專利技術,如果是開放的技術,那又開放到什么程度? □ 維護成本是多少?會不會隨著時間的推移,它的維護成本會非常昂貴?畢竟,方案的花費不應該高于要解決的問題,否則就是一次失敗的投資。我們聽說,有個項目的合同是支持一個規則引擎,引擎一年的維護費用是5萬美元,但是這個數據庫只有30條規則。這也太貴了。 當你在考察一個框架(或者任何技術)的時候,或許會被它提供的各種功能吸引。接著,在驗證是否使用這個框架的時候,你可能只會考慮已經發現的另外一些功能。但是,你真的需要這些功能嗎?也許為了迎合你發現的功能,你正在為它們找問題。這很想站在結賬處一時沖動而買無用的小零碎(那也正是商場把那些小玩意兒放到那里的原因)。 不久前,Venkat遇到了一個項目。咨詢師Brad把一個專有框架賣給了這個項目的管理者。在Venkat看來,這個框架本身也許還有點兒意思,但是它根本不適合這個項目。 盡管如此,管理者卻堅決認為他們要使用它。Venkat非常禮貌地停手不干了。他不想成為絆腳石,阻礙他們的工作進度。一年之后項目還沒有完成——他們花了好幾個月的時間編寫代碼來維護這個框架,為了適應這個框架,他們還修改了自己的代碼。 Andy有過相似的經歷:他的客戶想完全透明地利用開源,他們擁有“新技術大雜燴”,其中的東西太多,以至于無法讓所有的部分協同工作。 如果你發現自己在做一些花哨的東西(比 如從頭創建自己的框架),那就醒醒吧,聞 聞煙味有多大,馬上該起火了。你的代碼 寫得越少,需要維護的東西就越少。 例如,如果你想開發自己的持久層框架,記住Ted Neward 的評論:對象—關系的映射就是計算機科學的越南戰場(Ted Neward 曾寫過The Vietnam of Computer Science 著名文章,逐一探討了對象—關系映射的缺點。——編者注) 根據需要選擇技術。首先決定什么是你需要的,接著為這些具體的問題評估使用技術。對任何要使用的技術,多問一些挑剔的問題,并真實地作出回答。 切身感受新技術就應該像是新的工具,可以幫助你更好地工作,它自己不應該成為你的工作。 平衡的藝術 n 或許在項目中真正評估技術方案還為時太早。那就好。如果你在做系統原型并要演示給客戶看,或許一個簡單的散列表就可以代替數據庫了。如果你還沒有足夠的經驗,不要急于決定用什么技術。 n 每一門技術都會有優點和缺點,無論它是開源的還是商業產品、框架、工具或者語言,一定要清楚它的利弊。 n 不要開發那些你容易下載到的東西。雖然有時需要從最基礎開發所有你需要的東西,但那是相當危險和昂貴的。 13.保持可以發布 “我們剛試用的時候發現一個問題,你需要立即修復它。放下你手頭的工作,去修復那個剛發現的問題,不需要經過正規的程序。不用告訴其他任何人——趕快讓它工作就行了。” 這聽起來似乎沒什么問題。有一個關鍵修復的代碼必須要提交到代碼庫。這只是一件小事,而且又很緊急,所以你就答應了。 修復工作成功地完成了。你提交了代碼,繼續回到以前那個高優先級的任務中。然后一聲尖叫。太晚了,你發現同事提交的代碼和你的代碼發生了沖突,現在你使得每個人都無法使用系統了。這將會發費很多精力(和時間)才能讓系統重新回到可發布的狀態。現在你有麻煩了。你必須告訴大家,你不能交付你承諾的修復代碼了。而魔鬼在嘲笑:“哈哈哈!” 這時候,你的處境會很糟糕:系統無法發布了。你弄壞了系統,或許會帶來更糟糕的后果。 1836年,當時的墨西哥總統安東尼奧·洛佩斯·德·圣安那將軍,率領部隊穿越得克薩斯州西部,追趕敗退的薩姆·休斯頓將軍。當圣安那的部隊到達得克薩斯州東南方向的布法羅河岸的沼澤地帶的時候,他命令自己的部隊就地休息。傳說中認為他是太過自信,甚至沒有安排哨兵。就在那個傍晚,休斯頓發動了突然襲擊,這時圣安那的部隊已經來不及編隊了。他們潰不成軍,輸掉了這場決定性的戰爭,從此永遠改變了得克薩斯州的歷史(http://www.sanjacinto-museum.org/The_Battle/April_21st_1836.)。 任何時候只要你沒有準備好,那就是敵人進 攻你的最佳時機。好好想一想,你的項目進 入不可發布狀態的頻率是多少?你的源代碼 服務器中的代碼,是不是像圣安那在那個決定性的黃昏——沒有進行編隊,遇到緊急情況無法立即啟動。 在團隊里工作,修改一些東西的時候必須很謹慎。你要時刻警惕,每次改動都會影響系統的狀態和整個團隊的工作效率。在辦公室的廚房里,你不能容忍任何人亂丟垃圾,為什么就可以容忍一些人給項目帶來垃圾代碼呢? 下面是一個簡單的工作流程,可以防止你提交破壞系統的代碼。 □ 在本地運行測試。先保證你完成的代碼可以編譯,并且能通過所有的單元測試。接著確保系統中的其他測試都可以通過。 □ 檢出最新的代碼。從版本控制系統中更新代碼到最新的版本,再編譯和運行測試。這樣往往會發現讓你吃驚的事情:其他人提交的新代碼和你的代碼發生了沖突。 □ 提交代碼。現在是最新的代碼了,并且通過了編譯和測試,你可以提交它們了。 在做上面事情的時候,也許你會遇到這樣一個問題——其他人提交了一些代碼,但是沒有通過編譯或者測試。如果發生了這樣的事情,要立即讓他們知道,如果有需要,可以同時警告其他的同事。當然,最好的辦法是,你有一個持續集成系統,可以自動集成并報告集成結果。 這聽起來似乎有點恐怖,其實很簡單。持續集成系統就是在后臺不停的檢出、構建和測試代碼的應用。你可以自己使用腳本快速實現這樣的方式,但如果你選擇已有的免費、開源的解決方案,它們會提供更多的功能且更加溫低能。有興趣的話,可以看一看Martin Fowler的文章(http://www.martinfowler.com/articles/continuousIntegration.html),或者是Mike Clark編著的圖書《項目自動化之道》【Cla04】。 再深入一點,假設你得知即將進行的一次重大修改很可能會破壞系統,不要人氣發生,應該認真地警告大家,在代碼提交之前,找出可以避免破壞系統的方法。選擇可以幫助你平滑地引入和轉換這些修改的方法,從而在開發過程中,系統可以得到持續的測試和反饋。 雖然保持系統可以發布非常重要,但不會總是那么容易,例如,修改了數據庫的表結構、外部文件的格式,或者休息的格式。這些修改,通常會影響應用的大部分代碼,甚至導致應用暫時不可用,直到大量的代碼修改完。盡管如此,你還是有辦法減輕這樣的痛苦。 為數據庫的表結構、外部文件,甚至引用它的API提供版本支持,這樣所有相關變化都可以進行測試。有了版本功能,所作的變化可以與其他代碼基相隔離,所以應用的其他方面仍然可以繼續開發和測試。 你也可以在版本控制系統中添加一個分支,專門處理這個問題(使用分之需要十分小心,不好的分值也許會給你帶來更多的麻煩。詳情可以查閱《版本控制之道——CVS》或《版本控制之道——Subversion》)。保持你的項目時刻可以發布。保證你的系統隨時可以編譯、運行、測試并立即部署。 切身感受你會覺得,不管什么時候,你的老板、董事長、質量保障人員、客戶或者你的配偶來公司參觀項目的時候,你都能很自信并毫不猶豫地給他們演示最新構建的軟件。你的項目一直處于可以運行的狀態。 平衡的藝術 □ 有時候,做一些大地改動后,你無法花費太多的時間和精力去保證系統一直可以發布。如果總共需要一個月的時間才能保證它一周內可以發布,那就算了。但這只應該是例外,不能養成習慣。 □ 如果你不得不讓系統長期不可以發布,那就做一個(代碼和框架的)分支版本,你可以繼續進行自己的實驗,如果不行,還可以撤銷,從頭再來。千萬不能讓系統既不可以發布,又不可以撤銷。 14.提早集成,頻繁集成. “只要沒有到開發的末尾階段,就不要過早的浪費時間去想如何集成你的代碼.至少也要等到開發差不多的時候,才開始考慮它,畢竟還沒有完成開發,為什么要操心集成的事呢?在項目的末尾,你有充裕的時間來集成代碼.”我們說過,敏捷的一個主要特點就是持續開發,而不是三天打魚兩天曬網似的工作,特別是在幾個人一起開發同一個功能的時候,更應該頻繁地集成代碼.很多開發者用一些美麗的借口,推測集成的時間.有時不過是為了多寫一些代碼,或者是另一個子系統還有很多的工作要完成.他們很容易就會這樣想:”現在手頭上的工作壓力夠大了,到最后我才能做更多的工作.才能考慮到其他代碼.”經常會聽到這樣的借口:”我沒有時間進行集成”或者”我在機器上設置集成環境太費事了,我現在不想做它.”但是,在產品開發過程中,集成是一個主要的風險區域,讓你的字系統不停的增長,不去做系統集成,就等于把自己置于越來越大的風險中.世界沒有你仍然會轉動.潛在的分歧會繼續增加.相反,盡可能早的集成也更容易發現風險.這樣風險及相關的代價就會相當低.而等的時間越長.你也就會越痛苦.作者之一 Venkat小時候生活在印度欽奈市(Chennai),經常趕火車去學校.想其它的大城市一樣.印度的交通非常擁擠.他每次必須在車還沒有停穩的時候.就跳上去或者跳下來.但,你不能從站的地方一下子跳上運行的火車.我們在物理課上學習過這種運動規律.而應該是,首先你要沿著火車行駛的方向跑.邊跑邊抓住火車上的扶手.然后跳入火車中.軟件集成就想這一樣.如果你不斷地獨立開發.忽然有一天跳到集成這一步,千萬不要為受到打擊而吃驚.也許你自己在項目中就有這樣的體會:每次到項目結束的時候都覺得非常不爽.大家需要日日夜夜地進行集成.當你在公司昏天黑地地加班時,唯一的好處就是可以享受到免費的披薩. 獨立開發和早期集成之間是具有張力的.當你獨立開發時,會發現開發速度更快,生產率更高.你可以更有效地解決出現的問題.但那并不是意味著要你避免或延遲集成(見本頁側邊欄).你一般需要每天集成幾次,最好不要2~3天才集成一次. 當早期就進行集成的時候.你會看到子系統之間的 互交和影響,你就可以估算它們之間通信和共享的信息 數據.你越早弄清楚這些問題.越早解決它們.工作量就越 小.就好比是,剛開始有3個開發者,開發著5萬行的代碼. 后來是5000個開發者進行3000萬行代碼的開發.相反.如果你推遲集成的時間.解決這些問題就會變得很難.需要大量和大范圍地修改代碼.會造成項目延期和一片混亂. 提早集成.頻繁集成.代碼集成是主要的風險來源.要想規避這個風險.只有提早集成.持續而有規律地進行集成.切身感受如果你真正做對了.集成就不再會是一個繁重的任務.它只是編寫代碼周期中的一部分.集成時產生的問題.都會是小問題并且容易解決. 平衡的藝術 □ 成功的集成就意味著所以的單元測試不停地集成.正如醫學院界西波克拉底的誓言:首先,不要造成傷害. □ 通常,每天要和同隊其他成員以前集成代碼好幾次,比如平均每天5~10次,甚至更多.但如果你每次修改一行代碼就集成一次,那效用肯定會縮水.如果你發現自己的大部分時間都在集成,而不是寫代碼,那你一定是集成得過于頻繁. □ 如果你集成得不夠頻繁(比如,你一天集成一次,一周一次,甚至更糟),也許就會發現整天在解決代碼集成帶來的問題.而不是在專心寫代碼.如果你集成的問題很大.那一定是做得不夠頻繁. □ 對那些原型和實驗代碼,也許你想要獨立開發,而不要想在集成上浪費時間.但是不能獨立開發太長的時間.一旦你有了經驗.就要快速地開始集成. 15.提早實現自動化部署 “沒問題,可以手工安裝產品,尤其是給質量保證人員安裝,而且你不需要經常自己動手,他們都很擅長復制需要的所有文件。” 系統能在你的機器上運行,或者能在開發測試者和測試人員的機器上運行,當讓效果很好,當然很好。但是它同時也需要能夠部署在用戶的機器上。如果系統能運行在開發服務器上,那很好,但是它同時也要運行在生產環境中。 這就意味著,你要能用一種可重復和可靠的方式,在目標機器上部署你的應用。不幸的是,大部分開發者智慧在項目的尾期才開始考慮部署問題。結果經常出現部署失敗,要么是少了依賴的組建,要么是少了一些圖片,要么就是目錄結構有誤。 如果開發者改變了應用的目錄結構,或者是在不同的應用之間創建和共享圖片目錄,很可能會導致安裝過程失敗。當這些變化在人們印象中還很深的時候,你可以快速的找到各種問題。但是幾周或者幾個月之后查找它們,特別是在給客戶演示的時候,可就不是一件鬧這好玩的事情了。 如果現在你還是手工幫助質量保證人員安裝應用, 花一些時間,考慮如何將安裝過程自動化。這樣, 只要用戶需要,你就可以隨時為他們安裝系統。 要提早實現它,這樣質量保證團隊即可以測試應 用,又可以測試安裝過程。如果還可以手工安裝應用,那么最后把應用部署到生產環境時會發生什么尼?就算公司給你加班費,你也不愿意為不同用戶的機器或不同地點的服務器上一遍又一遍的安裝應用。 有了自動化部署系統后,在項目開發的整個過程中,會更容易適應互相依賴的變化。很可能你在安裝系統的時候,會忘記添加需要的庫或組建------在任意一臺機器上運行自動化安裝程序,你很快就會知道什么丟失了。如果因為為缺少了一些組建或者庫不兼容而導致安裝失敗,這些問題會很快浮現出來。 一開始就實現自動化部署應用。使用部署系統安裝你的應用,在不同的機器上用不同的配置文件測試依賴的問題。質量保證人員要像測試應用一樣測試部署。 切身感受 這些工作都應該是無形的。系統的安裝或者部署應該簡單,可靠及可重復。一切都很自然。 平衡的藝術 ¨ 一般產品在安裝的時候,都需要有相應的軟,硬件的環境。比如,Java或Ruby的某個版本,外部數據庫或者操作系統。這些環境的不同很可能會導致很多技術支持的電話。所以檢查這些依賴關系,也是安裝過程的一部分。 ¨ 在沒有詢問并征的用戶的同意之前,安裝程序絕對不能刪除用戶的數據。 ¨ 部署一個緊急修復的bug應該很簡單,特別是在生產服務器的環境中。你知道這會發生,而且你不想在壓力之下,在凌晨3點半,你還在手工部署系統。 ¨ 用戶應該可以安全并且完整的卸載安裝程序,特別是在質量保證人員的機器環境中。 ¨ 如果維護安裝腳本變得很困難,那很可能是一個早期的警告,預示著----很高的維護成本(或者不好的設計決策) ¨ 如果你打算把持續部署系統和產品CD或者DVD刻錄機連接到一起,你就可以自動的為每個構建制作出一個完整且有標簽的光盤。任何人想要最新的構建,只要從架子上拿最上面的一張光盤安裝即可。 15.使用演示獲得頻繁反饋 “這不是你的過錯,問題出在我們的客戶——那些麻煩的最終客戶和用戶身上。他們不停地更改需求,導致我們嚴重地延期。他們一次就應該想清楚所有想要的東西,然后把這些需求給我們,這樣我們才能開發出令他們滿意的系統。這才是正確的工作方式。” 你時常會聽到一些人想要凍結需求。但 需求就像是流動著的油墨 是,現實世界中的需求就像是流動著的 油墨。你無法凍結需求,正如你無法 Requirements are as fluid as ink 凍結市場、競爭、知識、進化或者成長一樣。就算你真的凍結了,也很可能是凍結了錯的東西。如果你期望用戶在項目開始之前,就能給你可靠和明確的需求,那就大錯特錯了,趕快醒醒吧! 沒有人的思想和觀點可以及時凍結,特別是項目的客戶。就算是他們已經告訴你想要的東西了,他們的期望和想法還是在不停地進化——特別是他們在使用新系統的部分功能時,他們才開始意識到它的影響和可能發生的問題。這就是人的本性。 作為人類,不管是什么事情,我們都能越做越好,不過是以緩慢而逐步的方式。你的客戶也一樣。在給了你需求之后,他們會不停地研究這些功能,如何才能讓它們變得更好使用。如果,你覺得自己要做的所有工作就是按照用戶最初的需求,并實現了它們,但是在交付的時候,需求已經發生了變化,你的軟件可能不會令他們滿意。在軟件開發過程中,你將自己置于最大的風險中:你生產出了他們曾經要求過的軟件,但卻不是他們現在真正想要的。那最后的結果就是:驚訝、震驚和失望,而不是滿意。 幾年前的一次數值分析課上,老師要求Venkat使用一些偏微分方程式模擬宇宙飛船的運行軌線。 程序基于時間t的坐標點,計算出在時間t+8的位置。程序最后繪出來的軌線圖就是如圖4-1中的虛線。暫無圖片 我們發現,估算出來的宇宙飛船位置遠遠地偏離了它的真實位置。萬有引力不是只在我們計算的坐標點上才起作用。實際上,萬有引力一直起作用:它是連接的,而不是離散的。由于忽略了點之間的作用力,我們的計算不斷引入了誤差,所以宇宙飛船最后到達了錯誤的地方。 縮小點之間的間隔(就是δ的值),再運行計算程序,誤差就會減少。這時,估算的位置(如圖4-1中的實線)就是和實際位置很接近了。 同理,你的客戶的期望就像宇宙飛船的實際位置。軟件開發的成功就在于最后你離客戶的期望有多近。你計算的每個精確位置,就是一個給客戶演示目前已經完成功能的機會,也正是得到用戶反饋的時候。在你動身進入下一段旅程的時候,這些反饋可以用來糾正你的方向。 我們經常看到,給客戶演示所完成功能的時間與得到客戶需求的時間間隔越長,那么你就會離最初需求越來越遠。 應該定期地,每隔一段時間,例如一個迭代的結束,就與客戶會晤,并且演示你已經完成的功能特性。 如果你能與客戶頻繁協商,根據他們的反饋開發,每個人都可以從中受益。客戶會清楚你的工作進度。反過來,他們也會提煉需求,然后趁熱反饋到你的團隊中。這樣,他們就會基于自己進化的期望和理解為你導航,你編寫的程序也就越來越接近他們的真實需求。客戶也會基于可用的預算和時間,根據你們真實的工作進度,排列任務的優先級。 較短的迭代周期,會對頻繁的反饋有負面影響嗎?在宇宙飛船軌線的程序中,當δ降低的時候,程序運行就要發費更長的時間。也許你會覺得,使用短的迭代周期會使工作變慢,延遲項目的交付。 讓我們從這個角度思考:兩年來一直拼命地開發項目,直到快結束的時候,你和你的客戶才發現一個基礎功能有問題,而且它是一個核心的需求。你以為缺貨訂單是這樣處理的,但這完全不是客戶所想的東西。現在,兩年之后,你完成了這個系統,寫下了數百萬行的代碼,卻背離了客戶的期望。再怎么說,兩年來辛苦寫出的代碼有相當大部分要重寫,代價是沉重的。 相反,如果你一邊開發,一邊向他們演示剛完成的功能。項目進展了兩個月的時候,他們說:“等一下,缺貨訂單根本不是這么一回事。”于是,召開一個緊急會議:你重新審查需求,評估要做多大的改動。這時只要付很少的代價,就可以避免災難了。 要頻繁地獲得反饋。如果你的迭代周期是一個季節或者一年(那就太長了),就應把周期縮短到一周或者兩周。完成了一些功能和特征之后,去積極獲得客戶的反饋。 清晰可見的開發。在開發的時候,要保持應有可見(而且客戶心中也要了解)。每隔一周或者兩周,邀請所有的客戶,給他們演示最新完成的功能,積極獲得他們的反饋。 切身感受項目啟動了一段時間之后,你應該進入一種舒適的狀態,團隊和客戶建立了一種健康的富有創造性的關系。 突發事件應極少發生。客戶應該能感受到,他們可以在一定程度上控制項目的方向。 平衡的藝術 1 當你第一次試圖用這種方法和客戶一起工作的時候,也許他們被這么多的發布嚇到了。所以,要讓他們知道,這些都是內部的發布(演示),是為了他們自己的利益,不需要發布給全部的最終用戶。 2 一些客戶,也許會覺得沒有時間應付每天、每周甚至一個月一次會議,那么就定一個月。 3 一些客戶的聯絡人的全職工作就是參加演示會議。他們巴不得每隔1個小時就有一次演示和反饋。你會發現這么頻繁的會議很難應付,而且還要開發代碼讓他們看。縮減次數,只有在你做完一些東西可以給他們演示的時候,大家才碰面。定性的時候,不應該拿來演示,那只能讓人生氣。可以及早說明期望的功能:讓客戶知道,他們看到的是一個正在開發中的應用,而不是一個最終已經完成的產品。 17使用短迭代,增量發布 “我們為后面的3年制定了漂亮的項目計劃,列出了所有的任務和可交付的時間表。只要我們那時候發布了產品,就可以占領市場。” 統一過程和敏捷方法都使用迭代和增量開發。使用增量開發一次開發應用功能的幾個小組。每一輪的開發都是基于前一次的功能,增加為產品增值的新功能。這時,你就可以發布或者演示產品。 迭代開發是,在小且重復的周期里,你完成各種開發任務:分析、設計、實現、測試和獲得反饋,所以叫做迭代。 迭代的結束就標記一個里程碑。這時,產品也許可用,也許不可用。在迭代結束時,新的功能全部完成,你就可以發布,讓用戶真正地使用,同時提供技術支持、培訓和維護方面的資源。每次增加的新功能都會包含多次迭代。 根據Capers Jones的格言:“…..大型系 統的開發是一件非常危險的事情。”大 給我一份詳細的長期計劃,我就 型系統更容易失敗。它們通常不遵守迭 會給你一個注定完蛋的項目 代和增量開發的計劃,或者迭代時間太 Show me a detailed log-term plan, 長(更多關于迭代和演進開發的討論, and I’ll show you a project that’s 以及和風險的關系、生產率和缺點,可 doomed 以查閱Agile and Iterative Development:A Manager’s Guide[Lar04]一書)。Larman指出,軟件開發不是精細的制造業,而是創新活動。規劃幾年之后客戶才能真正使用的項目注定是行不通的。 對付大項目,最理想的辦法就是小步前進,這也是敏捷方法的 。大步跳躍大大地增加了風險,小步前進才可以幫助你很好地把握平衡。 在你周圍,可以看到很多迭代和增量開發的例子。比如W3C(萬維網聯盟)提出的XML規范DTD(Document Type Definitions,文檔類型定義),它用來定義XML文檔的詞匯和結構,作為原規范的部分發布。雖然DTD設計的時候就解決了很多問題,但是在真正使用的時候,又顯現出很多問題和限制。基于用戶的反饋對規范就有了更深一層的理解,這樣就誕生了更加高效的第二代解決方案,例如Schema。如果他們一開始就試圖進行一些完美的設計,也許就看不到XML成為今天的主流了——我們通過提早發布獲得了灼見和經驗。 大部分用戶都是希望現在就有一個夠用的軟件,而不是在一年之后得到一個超級好的軟件(可以參見《程序員修煉之道——從小工作專家》“足夠好的軟件”一節[HT00])。確定使產品可用的核心功能,然后把它們放在生產環節中,越早交到用戶的手里越好。 根據產品的特性,發布新的功能需要幾周或者幾個月的時間。如果是打算一年或者兩年再交付,你就無法應該重新評估和重新計劃。也許你要說,構建復雜的系統需要發費時間,你無法用增量的方式開發一個大型的系統。如果這種情況成立,就不要生產大的系統。可以把它分解成一塊塊有用的小系統——再進行增量開發。即使是美國國家航空天局(NASA)也使用迭代和增量開發方式開發用于航天飛機的復雜軟件(參見Design,Development,Integration: Space Shuttle Primary Flight Software System [MR84]). 詢問用戶,哪些是使用產品可用且不可缺少的核心功能。不要為所有可能需要的華麗功能而分心,不要沉迷于你的想象,去做那些華而不實的用戶界面。 有一堆的理由,值得你盡快把軟件交到用戶手中:只要交到用戶手里,你就有了收入,這樣就有更好的理由繼續為產品投資了。從用戶那里得到的反饋,會讓我們進一步理解什么是用戶真正想要的,以及下一步該實現哪些功能。也許你會發現,一些過去認為重要的功能,現在已經不再重要了——我們都知道市場的變化有多快。盡快發布你的應用,遲了也許它就沒有用了。 使用短迭代和增量開發,可以讓開發者更加注于自己的工作。如果別人告訴你有一年的時間來完成系統,你會覺得時間很長。如果目標很遙遠,就很難讓自己去專注于它。在這個快節奏的社會,我們都希望更快地得到結果,希望更快地見到有形的東西。這不一定是壞事,相反,她會是一件好事,只要把它轉化成生產率和正面的反饋。 圖4-2描述了敏捷項目主要的周期關系。根據項目的大小,理想的發布周期是幾周到幾個月。在每個增量開發周期里,應該使用短的迭代(不應該超過兩周)。每個迭代都要有演示,選擇可能提供反饋的用戶,給他們每人一份最新的產品副本。 增量開發。發布帶有最小卻可用功能塊的產品。每個增量開發中,使用1 ~ 4周左右迭代周期。 切身感受短迭代讓人感覺非常專注且具效率。你能看到一個實際并且確切的目標。嚴格的最終期限迫使你做出一些艱難的決策,沒有遺留下長期懸而未決的問題。 平衡的藝術 o 關于迭代時間長短一直是一個有爭議的問題。Andy增經遇到這樣一位客戶:他們堅持認為迭代就是4周的時間,因為這是他們學到的。但他們的團隊卻因為這樣的步伐而垂死掙扎,因為他們無法在開發新的代碼的同時又要維護很多已經完成了的代碼。解決方案是,在每4周的迭代中間安排一周的維護任務。沒有規定說迭代必須要緊挨著下一個迭代。 o 如果每個迭代的時間都不夠用,要么是任務太大,要么是迭代的時間太長了。用戶的需要、技術和我們對需求的理解,都會隨著時間的推移而變化,在項目發布的時候,需要清楚地反映出這些變化。如果你發現自己工作時還帶有過時的觀點和陳腐的想法,那么很可能你等待太長時間做調整了。 o 增量的發布必須是可用的,并且能為用戶提供價值。你怎么知道用戶會覺得有價值呢?這當然要去問用戶。 18:固定的價格就意味著背叛承諾“對這個 項目,我們必須要有固定的報價,雖然我們還不清楚項目的具體情況,但任要有一個標價,到星期一,我需要整個團隊的評估,并且我們必須要再年末交付真個項目。” 固定價格的合同會是敏捷團隊的一個難題,我們一直在談論如何用持續,迭代和增量的方式工作。但是現在卻有人跑過來,想提早知道她會花費多少時間及多少成本。 從客戶方來看,這完全是理所應當的。客戶覺得做軟件就好比是蓋一棟樓房,或者是鋪設一個停車場,等等。為什么軟件不能像建筑業等其他傳統的行業一樣呢? 也許它真的與建筑有很多相似之處------真正的建筑行業,但不是我們想象中的建筑業。根據英國1998年的一個研究,由于錯誤而返工的成本大約占整個項目成本的30%。這不是因為客戶的需求變化,也不是物理定律的變化,而是一些簡單錯誤。比如,橫梁太短,窗戶洞太大,等等。這些都是簡單并且為人熟悉的錯誤。 軟件項目會遭遇各種各樣的小錯誤,還要加上基礎需求的變化(不,我要的不是一個工棚,而是一棟摩天大樓),不同個體和團隊的能力差別費城巨大(20倍,甚至更多),當然,還不停地會有新技術出現(從現在開始,釘子就變成圓形的了)。 軟件項目天生就是變化無常的,不可 固 定 的 價 格 就 是 保 證 要 背 叛 承諾 重復。如果要提前給出一個該規定的 A fiaed price guarantees a broken 價格,就幾乎肯定不能遵守開化上的承 promise 諾。那么我們有什么可行的辦法呢? 我們能做更精確的評估嗎?或者商量出另一中約定。根據自己的處境,選擇不同的戰略。如果你的客戶一定要你預想確定項目的報價(比如政府合約),那么可能你需要研究一些重型的評估技術,比如COCOMO模型或者功能點分析法(Function Point analysis)。但他們不屬于敏捷方法的范疇,并且是它們也要付出代價。如果這個項目本質上和另一個項目十分相似,并且是同一個團隊開化的,那么你就好辦了:為一個用戶開化的簡單網站,也下一個會非常相似。但是,很多項目并不像上面所說的那么如意。大部分項目都是業務應用,一個用戶和另一個用戶都由著巨大的差別。項目的發掘和創造需要很多配合工作,或許你可以提供稍有不同的安排,試試下面的方法。o 主動提議先構建系統最初的、小的和有用的部分(用建筑來打個比方,就是先做個車庫)。挑選一系列小的功能,這樣完成第一次交付應該不多于6-8周。向客戶解釋,這時候還不是要完成所有的功能,而是要足夠一次交付,并能讓用戶真正使用。 o 第一個迭代結束時客戶有連個選擇:可以選擇一系列新的功能,繼續進入下一個迭代;或者可以取消合同,僅需支付第一個迭代的幾周費用,他們要么吧現在的成果扔掉,要么找其他的團隊來完成它。 o 如果他們選擇繼續前進。那這時候,應該就能很好的預測下一個迭代工作。在下一個迭代結束時候。用戶仍然有同樣的選擇機會;要么現在停止,要么繼續下一個迭代。對客戶來說,這種方式的好處是項目不可能會死亡。他們可以很早的看到工作的進度(或者不足之處)。他們總是可以控制項目,可以隨時停止項目,不需要繳納任何的違約金,他們可以控制線完成哪些功能,并能精確地知道需要花費多少資金。總而言之,客戶會承擔更低的風險。而你所做的就是在進行迭代和增量開化。基于真實的評估。讓團隊和客戶一起,真正地在當前項目中工作,做具體實際的評估。由客戶控制他們要的功能和預算。 切身感受你的評估數據會在整個項目中發生變化------他們不固定的。但是,你會覺得自信心在不斷增加,你會越來越清楚每個迭代可以完成的工作。隨著時間的推移,你的評估能力會不斷的提高。 平衡的藝術 o 如果你對答案不滿意,那么看看你是否可以改變問題。 o 如果你是在一個基于計劃的非敏捷環境中工作,那么要么考慮一個基于計劃且非敏捷的開化方法,要么換一個不同的環境。 o 如果你在完成第一個迭代開化之前,拒絕做任何評估,也許你會失去這個合同,讓位于哪些提供了評估的人,無論他們做了多么不切實際的承諾。 o 敏捷不是一味著“開始編碼,我們最終會知道何時可以完成”。你仍然需要根據當前的知識和猜想,做一個大致的評估,解釋如何才能到達這個目標,并給出誤差范圍。 o 如果你現在別無選擇,你不的不提供一個固定的價格,那么你需要學到真正好的評估技巧。 o 也許你會考慮在合同中確定每個迭代的固定價格,但迭代的數量是可以商量的,他可以根據當前的工作狀況進行調整 [又名工作條款說明(Statement of Work)]。 第5章 敏捷反饋一步行動,勝過千萬專家的意見.------Bill Nye,The Science Guy科普節目主持人 在敏捷項目中,我們小步前進,不停地收集反饋,時刻矯正自己.但是,這些反饋都是從何而來呢? 在上一章中,我們討論了與用戶一起緊密工作-----從他們那里獲得反饋,并且采取的行動,我們主要討論如何從其他渠道獲得反饋.按照Bill Nye的觀點,實踐是絕對必需的.我們遵循這一原則,確保你明確知道項目的正確狀態,而不是主觀臆測. 很多項目,都是因為程序代碼失控而陷入困境.修復bug導致了更多的bug,從而又導致了更多的bug修復,成堆的測試卡片最后會把項目壓垮.這時,我們需要的是經常的監督-----頻繁反饋以確保代碼不會變壞,如果不會更好,至少能像昨天一樣繼續工作.在第78頁,介紹如何讓守護天使業監督你的代碼. 但是,這也不能防止你設計的接口或API變得笨重和難用.這時,你就要先用它再實現它(從第82頁開始介紹) 當然,從第87頁開始,可以看到為什么不同環境,就有不同問題. 現在,你擁有了設計良好的API和干凈的代碼,就可以看看結果是否符合用戶的期望了.你可以通過自動驗收測試來保證代碼是正確的,并且一直都是正確的.我們從第90頁開始談論這個話題 人人都想清楚了解項目的進度狀況,但有很容易誤入歧途,要么是被一些難懂的指示器誤導,要么就是錯誤迷信華麗的甘特圖,PERT圖或者日歷工具.其實,你想要的是能度量真實的進度,我們會在第93頁介紹他. 盡管,我們經驗談論了在開發的時候,與用戶一起工作并及時得到用戶的反饋,但是在其他的比如產品發布之后的很長一段時間,你還是需要在傾聽用戶的聲音,我們會在第96頁詳細解釋. 守護天使 “你不必為單元測試花費那么多時間和精力.它只會拖延項目的進度.好歹,你也是一個不錯的程序員—單元測試只會浪費時間,我們現在正處于關鍵時刻.” 代碼在快速地變化.每當你手指敲擊一下鍵盤,代碼就會被改變.敏捷就是管理變化的,而且,代碼可能是變化最頻繁的東西. 為了應對代碼的變化,你需要持續獲得代碼健康狀態 的反饋:它是在做你期望的事情嗎?最近一次修改有沒有無 編寫能產生反饋的代碼 意中破壞了什么功能?這時,你就帶上守護天使,確保所有功能 Coding feedback 都能正常工作.要做到這樣,就需要自動化單元測試. 現在,一些開發者會對單元測試有意見:畢竟,有”測試”這個詞在里面,毫無疑問這應該是其他人做的工作.從現在開始,忘掉”測試”這個詞.就把它看作是一個極好,編寫能產生反饋的代碼的技術. 先回顧一下,在過去大部分開發者是如何工作的:你寫了一小塊代碼,然后嵌入一些輸入語句,來看一些關鍵變量的值.你也許是在調試器中或者基于一些樁(stub)程序來運行代碼.你手工查看所有的運行效果,來修復發現的所有問題,然后扔掉那些樁代碼,或者從調試器中退出,再去解決下一個問題. 敏捷式的單元測試正是采取了相同,相似的過程,并且還讓其更上一層樓.不用扔掉樁程序,你把它保存下來,還要讓其可以自動化地持續運行.你編寫代碼來檢查具體值,而不是手工檢查那些感興趣的變量. 用代碼來測試變量的具體值(以及跟蹤運行了多少個測試),已經是非常普通的做飯.你可以選擇一個標準的測試框架,來幫助你完成簡單的編寫和組織測試的工作,如java的Junit,C#或.NET的Nunit,測試web service的HttpUnit,等等.實際上,對如何你可以想象到的環境和語言都有對應的單元測試框架,其中的大部分都可以從來Http://xprogramming.com/software.htm上的列表中找到. 只要有單元測試,就要讓它們自動運行.也就是每次編譯或者構建代碼的時候,就運行一次測試.把單元測試的結果看作是和編譯器一樣—如果測試沒有通過(或者沒有測試),那就像編譯沒有通過一樣糟糕. 接下來就是在后臺架設一個構建機器,不懂獲得最新版本的源代碼,然后編譯代碼,并運行單元測試,如果有如何錯誤它會讓你及時知道. 結合本地單元測試,運行每個編譯,構建機器不斷編譯和運行單元測試,這樣你就用有了一個守護天使.如果出現了問題,你會立刻知道,并且這是最容易修復(也是成本最低)的時候. 一旦單元測試到位,采用這樣的回歸測試,你就可以隨意重構代碼.可以根據需要進行試驗,重新設計或者重寫代碼:單元測試會確保你不會意外地破壞如何功能.這會讓你心情舒暢,你不用每次寫代碼的時候都如履薄冰. 單元測試是最受歡迎者的一中敏捷實踐,有很多圖書和其他資料可以幫你起步.如果你是一個新手,建議閱讀<<單元測試之道>>(有java[Ht03]和C#[Ht04]版本).如果要進一步了解測試的一些竅門,可以看一下Junit Recipes[Rai04]. 如果需要自動化地連接單元測試(和其他一些有用的東西),可以閱讀<<項目自動化之道>>[Cla04].盡管它只要是關于java的,但也有類似的可以用與.Net環境或者其他環境的工具. 如果你仍然在尋找開始單元測試的理由,下面有很多. n 單元測試能及時提供反饋.你的代碼會重復得到鍛煉.但若修改或者重寫了代碼,測試用例就會檢查你是否破壞了已有的功能.你可以快速得到反饋,并很容易的修復他們. n 單元測試讓你的代碼更加健壯.測試幫助你全面思考代碼的行為,幫你練習正面和反面以及異常情況. n 單元測試是有用的設計工具.正如我們在實踐20中談論到的,單元測試有助于實現簡單的,注重實效的設計. n 單元測試是讓你自信的后臺.你測試代碼,了解它在各種不同條件下的行為.這會讓你在面對新的任務,時間緊迫的巨大壓力之下,找到自信. n 單元測試是解決問題是的探測器. 單元測試就像是測試印制電路板的示波鏡.當問題出現的時候,你可以快速地給代碼發送一個脈沖信號.這為你提供了一個很自然的發現和解決問題的方法(見習慣35,第136頁). n 單元測試是可信的文檔.當你開始學習新API的時候,它的單元測試是最精確的可靠的文檔. n 單元測試是學習工具.在你開始學習新API的時候,可以為這個API寫個單元測試,從而加深自己的理解.這寫學習用的測試,不僅能幫助你理解API的行為,還能保證你快速找到以后可能引入的,無法與現有代碼兼容的變化. 使用自動化的單元測試.好的單元測試能夠為你的代碼問題提供及時的警報.如果沒有到位的單元測試,不要進行任何設計和代碼修改. 切身感受 你依賴與單元測試.如果代碼沒有測試,你會覺得很不舒服,就像是在高空作業沒有系安全帶一樣. 平衡的藝術 n 單元測試是優質股,值得投資.但一些簡單的屬性訪問方法或者價值不大的方法,是不值得花費時間進行測試的. n 人們不編寫單元測試的很多借口都是因為代碼中的設計缺陷.通常,抗議越強烈,就說明越糟糕. n 單元測試只有在達到一定測試覆蓋率的時候,才能真正地發揮作用.你可以使用一些測試覆蓋率工具,大致了解自己的單元測試的翻蓋情況. n 不測試越多質量就會越高,測試必須要有效.如果測試無法發現任何問題,也許它們就是沒有測試對路. 先用它在來實現它 “請進,先完成所有的代碼庫,后面會有大量時間看到用戶是否是如何思考,現在只有把代碼仍過去就可以了,我保證它沒有問題” 很多成功的公司都是靠著“吃自己的狗食”活著,也就是說,如果要讓你的產品盡可能的好,自己先要積極地使用它 幸運的是,我們不是在狗食業務,但是,我們的業務是要創造出能調用的API和可以使用的接口,事實上,在你剛做完設計但還沒完成后面的實現的時候,應使用它,這個可行嗎? 使用 被稱為TDD(Test Driven Development,測試驅動開發的技術,你總是在一個失敗的單元測試后才開始編碼,測試)失敗要么是因為 測試方法不存在,要么是因為方法的邏輯不足以讓 編寫能產生反饋的代碼 測試通過。 Writer test before writing code先測試,你就會站在代碼用戶的角度去思考,而不僅僅是一個單純的實現者,這樣就是很大區別,你就會發現以為自己要使用它們,所以能設計一個更有,更一致的接口 除止之外,先寫測試有助于消除過度復雜的設計,讓你可以會考慮需要這些類,例如:TicTacToeBoard,Cell,Row,Colum,Player,User,Peg,Score和Rueles咱們從TicTacToeBoard類開始,它就代表了井字旗本身(從游戲的刻心邏輯而不是UI角度說) 這可能是TicTacToeBoard類的第一測試,是用C#在NUtil測試框架下編寫的, 它創造了一個游戲面板,用斷言來檢查游戲沒有結束。 【TestFixture】 Public class TicTaToeTest { Private TicTaToeTest board; [SetUp] Public void createBord() { Board=new TicTacToeTest(); } [Test] Public void TestCreateBoard() { Assert.IsNotNull(board); Assert.IsFalse(borad.Gameover); } }測試失敗,因為類TicTactoeBoard 還不存在,你會得到一個編譯錯誤,如果它通過了,你一定很驚訝,不是嗎?這也可能會發生,只是概率很小,但確實可能發生,在測試通過之前,先要確保測試是失敗的,目的是希望暴露出測試中潛伏在的bug. 下面我們來實現這個類。Public class TicTacToeBoard{ Public bool gameOver{ Get{ Return false; } } } 在屬性GameOver中,我們現在只返回false,一般情況下,你會用到必要色最少代碼讓測試通過,從某種角度上說,這就是在欺騙測試——你知道代碼還沒有完成。但是沒有關系,后面的測試會迫使你在返回來的繼承添加新的功能。 下一步是什么呢?首先,你必須決定誰先開始走第一步旗,我們就要設第一個比賽者。先為第一個比賽者先一個測試。 [Test] Public void TestSetPirstPlayer(){ //what should go here? } 這時,測試會迫使你做一個決定,在完成它之前,你必須決定如何在代碼中表示 比賽者,如何把它們分配到面板上。這里有一個主意。 Board。SetFirstPlayer(new Player(”Mark”),”x”); 這會告訴面板,游戲玩家Mark使用X。 這樣當然可以。你真的需要Player這個類。或者第一個玩家的名字嗎?也許,稍后你需要知道誰是贏家,但現在它還不是問題。YANG(你可以永遠都不需要它)原則說過,如果不是真正需要他的時候,你就不應該實行這個功能。基于這一點,現在還沒有足夠的理由表示你需要的理由表示你需要Player這個類。 別忘了,我們還沒有實行TicTacToeBoard類中的SetFirstPlayer()方法,并且 還沒有寫play這個類,我們仍然是寫一個測試,我們假設下面的代碼是用來設置第一個玩家的Board。SetFirstPlayer(”x”); 它表示設X為第一玩家,比第一版本需要簡單,但是,這個版本隱藏著風險,你可以傳任何字母給SetFirestPlayer()方法,這就是意味著你必須添加代碼來檢查參數是O還是X,并且需要知道如果它不是這兩個值的時候該如何處理。因此要進一步簡單化,我們有一個簡單的標志來標明第一個玩家是O還是X,知道了這個,我們現在就可以寫單元測試了 [Test] Public void TestSetFirstPlay(){ Board.FirstPlayPegIsx=true; Assert.IsTrue(board.FirstPlayerPegIsx); } 我們可以將 FirstPlayerpegIsx 設為布爾型的屬性,并把它設為期望的值。你看起來挺簡單,也容易使用,比復雜的Player類容易很多,測試寫好了。你就可以通過TicTacToeBoard類中實現FirestPlayerPegIsx屬性,讓測試通過。 你看,我們是以paayer類開始,最后卻只使用了簡單的布爾型的屬性。這是如何做到的呢?這種簡化就是編寫代碼之前讓測試優先實現的。 但記住,我們不是要扔掉好的設計,就只用大量的布爾型來編碼所有的東西。這里重點是:什么是成功的實現待定功能的最低成本,總之,程序員很容易走向另一個極端—— 一些不必要的過于復雜的事情——測試優先會幫助我們,防止我們走偏。 消除那些還沒有編寫的類,這會很容易的簡化代碼。相反,一旦你已經編寫了代碼,也許會強迫自己保留這些代碼,并繼續使用它|(即使代碼已經過期作廢很久了) 移動設計并不意味著需要更多的類 Good design cloesn’t me 當你開發設計面向對象的系統的時候, 可能會迫使自己使用對象,有一種傾向 認為,面向對象的系統應該有對象的系統 應該對象組成,我們迫使自己創建越來越多 的對象類,不管他們是否真的需要,添加物 用代碼總是不好的想法TDD有機會讓你編寫的代碼之前(或者至少深入到實現之前),可以深思熟慮將如何用它,這會迫使你去思考它的可用性和便利性,并然你的設計更加注重實效。 當然,設計不是開始編碼的時候就結束了,你需要在它色生命周期中持續地添加測試,添加代碼,并重新設計代碼(更多信息參考第133也習慣28) 先用它在來實現它。將TDD作為設計工具,它會為你帶來更簡單游實數的設計 切身感受 這種感受就是,只在有具體理由的時候才開始編碼,你可以專注設計接口。而不會被很多實現的細節干擾 平衡的藝術 n 不要把測試優先和提交代碼之前的測試等同起來,測試先行可以幫助你改進設計,但是你還是需要在提交代碼之前做的測試。 n 任何一個設計都可以被改進 n 你在驗證一個想法或者設計一個原型的時候,單元測試也許不適合,但是。萬一這些代碼不幸倉促演變成一個真正的系統,就必須要為他們添加測試(但是最后重新開始設計系統) n 單純的單元測試無法保證好的設計,但它們會對設計有幫助,會然設計更加簡單。 不同環境,就有不同問題 “只要代碼能在你的機器上運行就可以了,誰會去關心她是否可以在其他平臺上工作,你又不用其他平臺。”如果廠商或者同事說了這樣的套話:“哦,那不會有什么不同。”你可以打賭,他們錯了,只要環境不同,就哼可能會有不同的問題 Ventkat真正在項目中學到了這一課,他的一個同事抱怨說mVenkat的代碼失敗了。但奇怪的是,問題在于,這于再Venkat機器上通過的一個測試一模一樣。實際上,它在一臺機器上可以運行,在另一臺機器上就不工作。 最后,他們終于找到罪魁禍首:一個NET環境下的API在Windows XP 和Window3002上的行為不同。平臺的不同,造成了結果不一樣 他們算是幸運的,能夠偶然發現這個問題,否則,很可能在產品投入使用的時候才會發現。如果很晚才發現這個問題,成本會非常昂貴———現象一下產品發表之后,才發現他并不支持應該支持的平臺,那會怎么樣 也許,你會要求測試團隊在所有支持的平臺上進行測試,如果他們的手工精細測試,可能并不是最可靠的測試辦法,我們需要根據面向開發者的測試辦法 你已經編寫了單元測試,測試你的代碼,每次在修改或者重構代碼的時候在提交代碼之前,你會運行測試辦法,那么現在所要做的就是各種支持的平臺和環境運行這些測試用例。 如果你的應用程序在不同操作系統上運行(例如 MacOs,Linux,Windows等),或者一個操作系統的不同版本(例如:Windows 2000,Windows XP ,Windows 2003 等)你需要測試所有的操作系統。如果你的應用程序要在不同的版本的Java虛擬機或者不同的NET CLR中運行,你也需要測試他們。 但是,也許你已經有時間壓力了,因此,你怎么可能喲時間在多個平臺上運行測試呢?這就要靠持續集成來拯救了。 使用自動化會節省時間 Automate to save time 我們在前面的保持可以發布中學過,用一個持續集成工具,周期性的從源代碼控制系統中取得代碼,并運行代碼,如果有任何測試失敗了,它會通知相關的開發者,通知方式可能是電子郵件,頁面,RSS Feed,或者其他一些新穎的方式! 要在多個平臺上測試,你只要為每個平臺設置持續集成系統就行了,但你或者同事提交代碼,測試會在每個平臺上自動運行,這樣,提交代碼之后的幾分鐘,你就可以知道它是否可以在不同的平臺上運行!這是多么英明的辦法呀! 構建機器的硬件成本相當于開發人員的幾個小時而已。如果需要,你甚至可以是用VMware或Virtual PC這樣的虛擬機產品,在一臺機器上運行不同版本的操作系統 ,VM或CLR 不同環境有不同問題。使用持續集成工具,在每一種支持的平臺和環境中運行單元測試,要積極地尋找問題,而不是等問題來找你 切身感受 感覺就像是單元測試,非但如此,而且還是跨越不同的世界的單元測試 平衡的藝術 n 不硬件比開發人員的時間更便宜,但如果你有很配置,要支持大量的平臺,可以選擇哪些平臺需要內部測試。 n 只因為不同的棧層順序,不同的單詞大小寫但而過,就能發現很多的平臺上的bug。因此,即使運行 Solaris的客戶比用Linux的少的很多,你仍然要在兩個系統上進行測試 n 你不希望因為一個錯誤而收到5次通過轟炸(這就像雙重征稅,會導致電子游戲疲勞癥)。可以設置一個主構建平臺或者配置,降低其他的構建服務器的運行頻率,這樣在它失敗的時候,你就喲足夠多的時間來修復構建平臺,或者匯總所以錯誤報告信息到一個地方,進行統一處理 自動驗收測試 “很好,你現在用單元測試來驗證代碼是否完成了你期望的行為,發給客戶吧。我們很快會知道這是否算是用戶期望的功能。” 你于用戶一起工作,開發他們想要的功能,但現在,你要能確保他們得到的數據庫是正確的,至少在用戶看來它是正確的。 幾年前,Andy做了一個項目,在項目中,他們的行業標準規定凌晨12:00點式一天的最后一分鐘,在12:01是一天的最早一分鐘(一般情況下,商業計算機系統任務凌晨11:59是一天的最后一分鐘,12:00是一天的最早的一分鐘)在驗收測試的時候這個很小的細節導致一個嚴重的問題——無法進行正確的合計。 關鍵業務邏輯必須要獨立精細嚴格的測試,并且最后需要通過用戶的審批。 但你也不可能拉這用戶,逐一檢查每個單元測試運行結果。實際上你需要能自動比較用戶期望和實際完成的工作 有一個辦法是可以是驗收測試不同意單元測試你應該讓用戶在不必學習編碼的情況下,根據自己的需要進行添加,更新和修改數據,你有很多方法來實現它。 Andy使用了一下架構,把測試數據放到一個頁面文件中并且用戶可以直接修改這些實際。Venkat實現Excel做過類似的事情,根據環境的不同,也可以找出一種能然偶那個號自然接收的方法(數據可以在頁面的文件Excel 文件,數據庫中)。或者可以考慮選擇一個現成的測試工具,它們會會你完成很多功能。 FIT,即集成測試框架,它很實用,可以更容易的使用HTML表格定義的測試用例,并比較測試結果的數據。 中運行,你也需要測試他們。 使用FIT,客戶可以定義帶有新功能的使用樣本,客戶,測試人員和開發人員(根據樣本)都可以成績表格,為代碼描述可能的輸入和輸出值,開發人員會參照帶有正開發的代碼結果在FIT表格中的樣本編寫測試代碼,測試結果成功或者失敗都會顯示在HTML教程頁面中,用戶可以很方便的查閱。 如果領域專家提供了業務的算法,運算后者方程式,為他們實現一套可以獨立運行的測試(參考第136頁習慣35)。要讓這些測試都成為測試套件的一部分,你會在項目生命周期保持實現為他們提供正確的答案。 為刻心的業務邏輯創建測試。讓你的客戶單獨驗證這些測試,要讓他們像一般的測試一樣可以自動運行。 切身感受 它像是協作完成的單元測試:你仍然是在編寫測試,但從其他人那里獲取答案 平衡的藝術 n 不是所有客戶都恩能夠給你提供正確的數據,如果他們已經有了正確的數據,就根本不需要新的系統了。 n 你也許會在酒系統(運行時電腦系統,也是是人工系統)中發現一起根本不知道的bug,或者一起不存在的真正的問題 n 是客戶的業務邏輯,但是不要陷于無邊無際的文檔寫作之中。 度量真實的進度 “用自己的時間表報告工作進度,我們會用它做項目計劃,不用管那些時間的工作時間,每周填滿40小時就可以了。” 時間的消逝(通常很快)可以證明:判斷工作進度最后是看實際花費的時間而不是估計的時間。 哦,你說早已經用時間進行了追蹤。不幸的是,幾乎所有公司的時間表都是會工資會計準備的,不是用度量軟件項目的開發進度的。例如:如果你工作了60個小時,也許你的老板會讓你在時間表上只填寫40個小時,這是公司會計想到的,所以,時間表很難真實的反映工作完成的狀況,因此它不可以用來進行羨慕計劃,評估或表現評估。 專注于你的方向 Focus on where your.re going 即使沒有時間表,一下開發人員還是很難面對現實了解自己的真實進度,你曾經聽到開發人員報告一個任務完成了80%嗎?然而過了一天又一天,一周又周,那個任務仍然是完成了80%?隨意用一個比率進行度量是沒有意義的,這就好比說80%是對的(除非你是政客,否則對和錯應該是布爾條件)。所以,我們不應該去計算工作完成的百分比,而應該測試還剩下多少工作量沒有完成。如果你最初估計這個任務需要40個小時,在開發35個小時之后,你認為你還需要另外30個小時的工作。那就得到了最重要的度量結果(這里誠實非常重要,隱瞞真相毫無意義)。 在你最后真正完成一項任務時,要清楚知道完成這個任務真正花費的時間。奇怪的是,它花費的時間很有可能要比最初估計時間長。沒有關系,我們希望這能作為下一次的參考,在為下一個任務估計工作量時,可以根據這次經驗調整評估,如果你低估了一個任務,評估師2天,它最后花費了6天,那么系數就是3。除非是異常情況,否則你應該對下一次估計乘以系數3。你的評估會波動一段時間,有時候過低估計,有時候會過高估計。但隨著時間的推移,你的評估會于事實接進 近,你也會對任務所花費的時間更清楚地認識。 運行,你也需要測試他們。 如果能一直讓下一步工作是可見的,會助于進度度量。組合的做法就是使用待辦事項(backlog)。 待辦事項就是等待完成的任務列表,當一個任務被完成了,它就會在列表中移動(邏輯上的,而物理上就是把它沖列表中劃掉或者標識它是完成的狀態)。當添加新新任務的時候,先排列他們的優先級,然后加入到待辦事項中。你也可以由各人的待辦事項,當前迭代的待辦事項或者整個項目的待辦事項。 通過代辦事項,就可以隨時知道下一步最重要的任務是什么。同事你的評估技巧也在不停的改進,你也會越來越清楚完成一些任務的花費的時間。 清楚項目的真實進度,是一項強大的技術。 度量剩下的工作量。不要用不恰當的度量來欺騙自己或者團隊,要評估那些需要完成的待辦事項。,要讓他們像一般的測試一樣可以自動運行。 切身感受 你會覺得很舒服,因為你很清楚哪些任務已經完成,哪些是還沒有完成,以及他們的優先級 平衡的藝術 n 6分鐘作為一個時間單位,它的粒度實在是太細了,這不是敏捷的做法 n 一周或者一個月的時間單元,它的粒度太粗了,這不是敏捷的做法 n 關注功能,而不是日程表。 n 如果你在一個項目中花費了很多時間來了解你所花費的時間,而沒有足夠的時間進行工作,那么你在了解你所花費的時間上花費的時間久太多了,聽懂了嗎 n 一周工作40個小時,不是說你就有40個小時的編碼時間,你需要減少去會議,電話,電子郵件以其他相關活動的時間。 傾聽用戶的聲音 “用戶就是會抱怨,這不是你的過錯,是用戶太愚蠢了,連使用手冊都看不懂。它不是bug,只是用戶不明白如何使用而已,他們本應該知道更多”。 Andy曾經在一家大公司工作過,為高端的Unix工作站開發產品。在這個環境中,你不是簡單的運行setup.exe文件或者pkgadd命令,就可以完成軟件的安裝。你必須在工作站上復制文件并調整各種設置。 Andy和他的團隊成員們覺得一切工作的很順利。直到一天,Andy走過幾十支持部門的工作間,聽到一個技術支持工程師對著電話大笑:“哦,這不是bug,你只是犯了一個每個人都會犯得錯誤。”并且,不只是這一個工程師,整個部門都在嘲笑這些可憐,天真和愚蠢的客戶。倒霉的客戶必須要配置那些包含以下魔法數字的模糊系統文件, 否則系統根本運行不起來。系統即沒有錯誤提示消息,也不會 這是一個bug 破潰,只是顯示大黑屏和一個斗大的“退出”按鈕。事實上,安 this is a bug 裝說明書中有一行提到了這樣的問題,但顯然80%的用戶忽略 了這個信息,因此只能求助公司的技術支持部門,并遭到他們的嘲笑. 正如我們在第128頁第7章中所說,當出了錯誤,你要盡可能地提供詳細信息.黑屏和含義不明的”退出”按鈕是很不友好的行為.更糟糕的是,在得到用戶反饋的時候,還嘲笑用戶愚蠢,而不去真正地解決問題. 不管它是否是產品的bug,還是文檔的bug,或者是對用戶社區理解的bug,它都是團隊的問題.而不是用戶的問題. 下面是一個案例是:一個昂貴的專業車間的控制系統,沒有任何一個用戶會使用.因為,使用系統的第一步是要輸入用戶名和密碼,進行登錄.但這個車間的大部分工人都是文盲,沒有人去問過他們,也沒有去收集他們的反饋.就這樣,為用戶安裝了一個無用的系統.最后,花費巨大的費用,開發人員重新開發了一個基于圖片的使用界面. 我們花費了很大的精力從單元測試之類的代碼中獲得反饋,但卻容易忽略最終用戶的反饋.你不僅需要和真實用戶(不是他們的經理,也不是業務分析師之類的代理人)進行交談,還分需要耐心地傾聽. 即使他們說的內容很傻! 每一個抱怨的背后都隱藏了一個事實.找出真相,修復真正的問題. 切身感受 對客戶的那些愚蠢抱怨,你既不會生氣,也不會輕視,你會查看一下,找出背后真正的問題. 平衡的藝術: n 沒有愚蠢的用戶. n 只有愚蠢,自大的開發人員. n “它就是這樣的.”這不是一個好的答案. n 如果代碼問題解決不了,也許可以考慮通過修改文檔或者培訓來彌補. n 你的用戶有可能會閱讀所有的文檔,記住其中的所有內容.但也可能不會. 第6章 敏捷編碼 任何一個笨蛋都能夠讓事情變得越來越笨重,越來越極端.需要天才的指點以及許多的勇氣,才能讓事情向相反的方向發展. -John Dryden, 書信集10: 至Congreve 新項目剛開始著手開發時,它的代碼很容易理解和上手.然而,隨著開發過程的推進,項目不知不覺中演變為一個龐然怪物.發展到最后,往往需要投入更多的精力,人力和物力來讓它繼續下去. 開始看起來非常正常的項目,是什么讓它最終變得難以掌控?開發人員在完成任務時,可能會難以底擋誘惑為節省時間而走”捷徑”.然而,這些”捷徑”往往只會推遲問題的爆發時間,而不是把它徹底解決掉(如同第15頁習慣2中的情況一樣).當項目時間上的壓力增加時,問題最終還是會在項目團隊面前出現,讓大家心煩意亂. 如何保證項目開發過程中壓力正常,而不是在后期面對過多的壓力,以致噩夢連連呢?最簡單的方式,就是在開發過程中便細心”照看”代碼.在編寫代碼時,每天付出一點小的努力,可以避免代碼”腐爛”,并且保證應用程序不至變得難以理解和維護. 開發人員使用本章的實踐習慣,可以保證開發出的代碼無論是在項目進行中還是在項目完成后,都易于理解,擴展和維護.這些習慣會幫助你對代碼進行”健康檢查”,以防止它們變成龐然怪物. 首先,第100頁中的習慣是:代碼要清晰地表達意圖.這樣的代碼清晰易懂,僅憑小聰明寫出的程序很難維護.注釋可以幫助理解,也可能導致不好的干擾,應該總是用代碼溝通(見105頁).在工程項目中沒有免費的午餐,開發人員必須判斷哪些東西更加重要,每個決策會造成的后果,也就是說要動態評估取舍(見第110頁)以得到最佳的決策. 項目是以增量方式進行開發的,寫程序時也應該進行增量式編程(見第113頁).在編寫代碼的時候,要想保持簡單很難做到---實際上,想寫出簡單的代碼要遠比寫出令人厭惡的,過分復雜的代碼難得多,不過這樣做絕對值得,見第115頁. 我們將在第117頁談到,良好的面向對象設計原則建議:應該編寫內聚的代碼.要保持代碼條理清晰,應該遵循如第121頁上所述的習慣:告知,不要詢問.最后,通過設計能夠根據契約進行替換的系統(見124頁),可以在不確定的未來中保持代碼的靈活性. 25 代碼要清晰地表達意圖 “可以工作而且易于理解的代碼當然好,但是讓人覺得聰明更加重要.別人給你錢是因為你腦子好使,讓我們看看你到底有多聰明.” 我們大概都見過不少難以理解和維護的代碼.而且(最壞的是)還有錯誤.當開發人員們像一群旁觀見到UFO一樣圍在代碼四周,同樣也感到恐懼,困惑與無助時,這個代碼的質量就可想而知了.如果沒有人理解一段代碼的工作方式,那這段代碼還有什么用呢? 開發代碼時,應該更注重可讀性,而不是只圖自己方便.代碼閱讀的次數要遠遠超過編寫的次數和,所以在編寫的時候值得花點功夫讓它讀起業更加簡單.實際上,從衡量標準上來看,代碼清晰程度的優先級應該排在執行效率之前. 例如,如果默認參數或可選參九會影響代碼可讀性,使其更難以理解和調試,那最好明確地指明參數,而不是在以后讓人覺得迷惑。 在改動代碼以修復bug或者添加新功能時,應該有條不紊地進行。首先,應該理解代碼做什么,它是如何做的。接下來,搞清楚將要改變哪些部分,然后著手修改并進行測試。作為第1步的理解代碼,往往是最難的。如果別人給你的代碼很容易理解,接下來的工作就省心多了。要敬重這個黃金法則,你欠他們一份情,因此也要讓你自己的代碼簡單、便于閱讀。 明白地告訴閱讀程序的人,代碼都做了什么,這是讓其便于理解的一種方式。讓我們看一些例子。 coffeeShop.PlaceOrder(2); 通過閱讀上面的代碼,可以大致明白這是要在咖啡店中下一個訂單。但是,2到底是什么意思?是意味著要兩杯咖啡?要再加兩次?還是杯子的大小?要想搞清楚,唯一的方式就是去看方法定義或者文檔,因為這段代碼沒有做到清晰易懂。 所以我們不妨添加一些注釋。 coffeeShop.PlaceOrder(2/*large cup*/); 現在看起來好一點了,不過請注意,注釋有時候是為了幫寫得不好的代碼補漏(見第105頁習慣26:用代碼溝通)。 Java5與.NET中有枚舉值的概念,我們不妨使用一下。使用C#,我們可以定義一個名為CoffeeCupSize的枚舉,如下所示。 public enum CoofeeCupSize {Small,Medium,Large } 接下來就可以用它來下單要咖啡了。 coffeeShop.PlaceOrder(CoffeeCupSize.Largxe); 這段代碼就很明白了,我們是要一個大杯的咖啡。 作為一個開發者,應該時常提醒自己是否有辦法讓寫出的代碼更容易理解。下面是另一個例子。 Line 1 public int compute(int val) {int result = val<<1;//…more code…return result; } 但對沒有類似背景的人們來說,又會如何—他們能明白嗎?也許團隊中有一些剛剛轉行做開發、沒有太多經驗的成員。他們會撓頭不已,直到把頭發抓下來。代碼執行效率也許很高,但是缺少明確的意圖和表現力。 用位移做乘法,是在對代碼進行不必要且危險的性能優化。Result-val*2看起來更加清晰 也可以達到目的,而且對于某種給定的編譯器來說,可能效率更高(懂得丟棄,見34頁習慣7)。不要表現得好像很聰明似的,要遵循PIE原則:代碼要清晰的表達意圖。 要違反了PIE原則,造成的問題就不只是代碼可讀性那么簡單了——它會影響到代碼的正確性。下列代碼是一個C#方法,試圖同步對CoffeeMaker中MakeCoffee()方法進行調用。 Public void MakeCoffee {Lock(this) {//…operation } } 這個方法的作者想設置一個臨界區(critical section)——任何時候最多只能有一個線程來執行操作中的代碼,要達到這個目的,作者在CoffeeMaker實例中聲明了一個鎖。一個線程只有獲得這個鎖,才能執行這個方法。(在JAVA中,會使用synchronized而不是lock,不過想法是一樣的。) 對于java或NET程序員來說,這樣寫順理成章,但是其中有兩個小問題。首先,鎖的使用影響范圍過大;其次,對一個全局可見的對象使用了鎖,我們進一步來看看這兩個問題。 假設CoffeeMaker 同時可以提供熱水,因為有些人希望早上能夠享用一點伯爵紅茶。我想同步GetWater()方法,因此調用其中的lock(this)。這會同步任何在CoffeeMaker上使用lock的代碼,也就意味著不能同時制作咖啡以及獲取熱水。這是開發者原本的意圖嗎?還是鎖的影響范圍太大了?通過閱讀代碼并不能明白這一點,使用代碼的人也就迷惑不已了。 同時,MakeCoffee()方法的實現在CoffeeMaker對象上聲明了一個鎖,而應用的其他部分都可以訪問CoffeeMaker對象。如果在一個線程中鎖定了CoffeeMaker對象實例,然后在另外一個線程中調用那個實例之上的MakeCoffee()方法呢?最好的狀況也會執行效率很差,最壞的狀況會帶來死鎖。 讓我們在這段代碼上應用PIE原則,通過修改讓它變得更加明確吧。我們不希望同時有兩個或更多的線程來執行MakeCoffee()方法。那為什么不能為這個目的創建一個對象并鎖定它呢? private Object makeCoffeeLock = new Object(); public void MakeCoffee() {Lock(makeCoffeeLock){// … operation } } 這段代碼解決了上面的兩個問題 — 我們通過指定一個外部對象來進行同步操作,而且更加明確地表達了意圖。 在編寫代碼時,應該使用語言特性來提升表現力。使用方法名來傳達意向,對方法參數的命名要幫助讀者理解背后的想法。異常傳達的信息是哪些可能會出現問題,以及如何進行防御式編程,要正確地使用和命名異常。好的編碼規范可以讓代碼變得易于理解,同時減少不必要的注釋和文檔。 要編寫清晰的而不是討巧的代碼。向代碼讀者明確表明你的意圖。可讀性差的代碼一點都不聰明。 切身感受應該讓自己或團隊的其他任何人,可以讀懂自己一年前寫的代碼,而且只讀一遍就知道它的運行機制。 平衡的藝術 □ 現在對你顯而易見的事情,對別人可能并非如此,對于一年以后的你來說,也不一定顯而易見。不妨將代碼視作不知道會在未來何時打開的一個時間膠囊。 □ 不要明日復明日。如果現在不做的話,以后你也不會做的。 □ 有意圖的編程并不是以為著創建更多的類或者類型。這不是進行過分抽象的理由。 □ 使用符合當時情形的耦合。例如,通過散列表進行松耦合,這種方式適用于在實際狀況中就是松耦合的組件。不要使用散列表存儲緊密耦合的組件,因為這樣沒有明確表示出你的意圖。 26.用代碼溝通 “如果代碼太雜亂以至于無法閱讀,就應該使用注釋來說明.精確解釋代碼做了什么,每行代碼應該加注釋,不用關為什么要這樣編碼,只要告訴我們到底是怎樣做的就好了.” 通常程序員都很討厭文檔,這是因為大部分文檔都與代碼沒有什么關系,并且越來越難保證其符合目的的最新情況.這不是違反了DRY原則(不要重復你自己Don’t Repeat Yourself ,見[HTOO]),還會產生是人誤解的文檔,這還不如沒有文檔. 建立代碼文檔無外乎兩種方式.利用代碼本身,利用注釋來溝通代碼之外的問題.. 如果必須通讀一個方法的代碼才能了解它在做了什么 ,那么開發人員要投入大量的時間和精力才能完成它. 不需要注釋來包裹你 的代碼的代碼反過來講,只需短短幾行注 釋說明方法 Don't comment to cover up 行為,就可以讓生活變得輕松許多.開發人員可以很快 了解它的意圖.它的期待結果,以及應該注意之處------這可省了你不少勁兒. 應該文檔化你所有的代碼嗎?在某種程度上說,是的.但這并不意味著要注釋絕大部分代碼,特別是在方法體內部.源代碼可以被讀懂,不是因為其中的注釋,而應該是由于本身優雅而清晰------變量名運用正確.空格使用得當,邏輯分離清晰,以及表達式非常簡潔. 如何命名很重要,程序元素的命名是代碼讀者必須的部分.通過使用細心挑選的名稱,可以向閱讀者傳遞大量的意圖和信息.反過來講,使用人造的命名范式會讓代碼難以閱讀和理解.這些范式中包括的底層數據類型信息.會硬編碼在變量名和方法名中.形成脆弱,僵化代碼,并會在將來造成麻煩. 使用細心 挑選的名稱和清晰的執行路徑.代碼幾乎不需要注釋.實際上,當Andy和 Dave Thomas聯手寫作第一本關于Ruby編程語言的書籍時(即參考文獻),他們只要閱讀將會在Ruby解釋器中執行幾行代碼.幾乎就可以把整個Ruby語言的相關細節記錄下來.代碼能夠自解釋.而不是依懶注釋.是一件很好的事情,Ruby在創建者松本行弘是日本人.而Andy和Dave除了sukiyaki和sake之外一句日語都不會. 如何界定一個好的命名呢?良好的命名可以相讀者提供大量的正確的信息.不好的命名不會傳達任何的信息.,糟糕的命名則會傳遞錯誤的信息.. 例如:,一個命名為readAccount()的方法實際所做的卻是向硬盤寫入地址信息.這樣的命名則是被認為是最糟糕的(是的這確實發生過,參見[HTOO]). Foo是一個具有歷史意義,很棒的臨時變量名稱.但是它沒有傳遞作者的任何意圖.要盡量的避免這種神秘的變量名.不是說命名短小就等于神秘.在許多的編程語言中.通常使用i來表示循環索引變量,s常被用來表示一個字符串.這在許多的語言中都是慣用的用法.雖然很短小.但是并不神秘.在這些壞境中使用s作為循環索引變量.可真不是什么好的主意,名為indexvar的變量也同樣不好.不必費盡心機去用繁復冗長的名字替換大家已習慣的名稱. .對于顯而易見的代碼,增加注釋,也會有同樣的問題,比如在一個類的構造器方法后面加這是//Constructor就多此一舉,但是很不幸,這種注釋很常見---通常是用于熱心的IDE插入的.最好的狀況下.他不過是為代碼添加.了“噪音”.最壞的情況下,隨著時間的推進這些注釋則會過時,變得不在正確. 許多的注解沒有傳遞任何有意義的信息.例如,對于passthorugh()方法它的注釋是”這個方法允許你傳遞”,但是讀者能從中的到什么信息呢?這種注釋只會分散注意力.而且很容易失去效性[假使方法最后方法有被命名為sendToHost()] 注釋可以用來為讀者指定一條正確的代碼訪問路線圖.為代碼中的每類或者模塊添加一個短小的描述.說明其中的目的以及是否有任何特別的需求.對于類中的每個方法可能要說明下列信息. ¨ 目的: 為什么需要這個方法? ¨ 需求(前置條件): 方法需要什么樣的輸入,對象必須處于何種狀態.才能讓這個方法工作. ¨ 承若(后置條件): 方法成功執行后,對象處于什么樣的狀態.有那些返回值. ¨ 異常: 可能會發生什么樣的問題.會拋出什么樣的異常. 要感謝如RDoc,javadoc和ndoc這樣的工具,使用他們可以很方便的直接從代碼注釋創建有用的,格式優美的文檔.這些工具抽取注釋,并生成樣式漂亮且帶有超鏈接的HTML輸出. 使用注釋溝通:使用細心的選擇的,有意義的命名.用只是描述代碼的意圖和約束.注釋不能代替優秀的代碼. 切身感受  注釋就像是可以幫助你的好朋友,可以先閱讀注釋,然后快速瀏覽代碼.從而完全理解它做了什么.以及為什么這樣做? 平衡的藝術 ¨ pascal定理的創始人Blaise Pascal曾說,他總是沒有時間寫短息.所以只好寫長信.請話花時間去寫簡單扼要的注釋吧! ¨ 在代碼可以傳遞意圖的地方不要寫注釋. ¨ 解釋代碼做了什么的注釋用處不那么大.相反,注釋要說明什么為什么會這樣寫代碼. ¨ 當中寫方法時,保留描述原有的方法意圖和約束的注釋. 27.動態評估取舍 “性能、生產力、優雅、成本以及上市時間,在軟件開發過程中都是至關重要的因素。每一項都必須達到最理想狀態。” 你可曾經身處這樣的團隊:管理層和客戶將很大一部分注意力都放在應用的界面展示上。也有這樣的團隊,其客戶認為性能表現非常重要。在團隊中,你可能會發現,有這樣一個開發主管或者架構師,他會強調遵守“正確”的范式比其他任何事情都重要。對任何單個因素如此獨斷地強調,而不考慮它是否是項目成功的必要因素,必然因素,必然導致災難的發生。 強調性能的重要性情有可原,因為惡劣的性能表現會讓一個應用在市場上鎩羽而歸。然而,如果應用的性能已經足夠好了,還有必要繼續投入精力讓其運行得更快一點嗎?大概不用了吧。一個應用還有很多其他方面的因素同樣重要。與其發費時間去提升千分之一的性能表現,也許減少開發投入,降低成本,并盡快讓應用程序上市銷售更有價值。 舉例來說,考慮一個必須要與遠程Windows服務器進行通訊的.NET Windows應用程序。可以選擇使用.NET Remoting技術或Web Service來實現這個功能。現在,針對使用Web Service的提議,有些開發者會說:“我們要在Windows之間進行通信,通常此類情況下,推薦使用.NET Remoting。而且,Web Service很慢,我們會遇到性能問題。”嗯,一般來說確實是這樣。 然而,在這個例子中,使用Web Service很容易開發。對Web Service的性能測試表明XML文檔很小,并且相對應用程序自己的響應時間來講,花在創建和解析XML上的時間幾乎可以忽略不計。使用Web Service不但可以在短期內節省開發時間,且在此后團隊被迫使用第三方提供的服務時,Web Service也是個明智的選擇。 考慮這樣一個應用,從數據庫中讀取數據,并以表格方式顯示。你可以使用一種優雅的、面向對象的方式,從數據庫中去數據,創建對象,再將它們返回給UI層。在UI層中,你再從對象中拆分出數據,并組織為表格方式顯示。除了看起來優雅之外,這樣做還有什么好處嗎? 也許你只需要讓數據層返回一個數據集(dataset)或數據集合,然后用表格顯示這些數據即可。這樣還可以避免對象創建和銷毀所耗費的資源。如果需要的只是數據展示,為什么要創建對象去自找麻煩呢?不按書上說的OO方式來做,可以減少投入,同時獲得性能上的提升。當然,這種方式有很多缺點,但問題的關鍵是要多長個心眼兒,而不是總按照習慣性的思路去解決問題。 總而言之,要想讓應用成功,降低開發成本與縮短上市時間,二者的影響同樣重要。由于計算機硬件價格日益便宜,處理速度日益加快,所以可在硬件上多投入以換取性能的提升,并將節省下來的時間放在應用的其他方面。 當然,這也不完全對。如果硬件需求非常龐大,需要一個巨大的計算機網格以及眾多的支持人員才能維持其正常運轉(比如類似Google那樣的需求),那么考慮就要向天平的另一端傾斜了。 但是誰來最終判定性能表現已經足夠好,或是應用的發展已經足夠“炫”了呢?客戶或是利益相關者必須進行評估,并做出相關決定(見第45頁習慣10)。如果團隊認為性能上還有提升的空間,或者覺得可以讓某些界面看起來更吸引人,那么就去咨詢一下利益相關者,讓他們決定應將重點放在哪里。 沒有適應所有狀況的最佳解決方案。你必須對手上 沒有最佳解決方案 的問題進行評估,并選出最合適的解決方案。每個 No best solution 設計都是針對特定問題的 — 只有明確地進行評 估和權衡,才能得出更好的解決方案。動態評估權衡。考慮性能、便利性、生產力、成本和上市時間。如果性能表現足夠了,就將注意力放在其他因素上。不要為了感覺上的性能提升或者設計的優雅,而將設計復雜化。 切身感受即使不能面面俱到,你也應該覺得已經得到了最重要的東西 — 客戶認為有價值的特性。 平衡的藝術 □ 如果現在投入額外的資源和精力,是為了將來可能得到的好處,要確認投入一定要得到回報(大部分情況下,是不會有回報的)。 □ 真正的高性能系統,從一開始設計時就在向這個方向努力。 □ 過早的優化是萬惡之源。 □ 過去用過的解決方案對當前的問題可能適用,也可能不適用。不要事先預設結論,先看看現在是什么狀況。 28.增量式編程 “真正的程序員寫起代碼來,一干就是幾個小時,根本不停,甚至連頭都不抬。不要停下來去編譯你的代碼,只要一直往下寫就好了!” 當你開車進行長途旅行時,兩手把住方向盤,固定在一個位置,兩眼直盯前方,油門一踩到底幾個小時,這樣可能嗎?當然不行了,你必須掌控方向,必須經常注意交通狀況,必須檢查油量表,必須停車加油、吃飯,準備其它必需品,以及諸如此類的活動。 如果不對自己編寫的代碼進行測試,保證沒有問題,就不要連續幾個小時,甚至連續幾分鐘進行編程。相反,應該采用增量式的編程方式。增量式編程可以精煉并結構化你的代碼。代碼被復雜化、變成一團亂麻的幾率減少了。所開發的代碼基于即時的反饋,這些反饋來自于小步幅方式編寫代碼和測試的過程。 采取增量式編程和測試,會傾向于創建更小的方法和更具有內聚性的類。你不是在埋頭盲目地一次性編寫一大堆代碼。相反,你會經常評估代碼質量,并不時地進行許多小調整,而不是一次修改許多東西。 在編寫代碼的時候,要經常留心可以改進的微小方面。這可能會改善代碼的可讀性。也許你會發現可以把一個方法拆成幾個更小的方法,使其變得更易于測試。在重構的原則指導下,可以做出許多細微改善(見Martin Fowler的《重構:改善既有代碼的設計》[FBB+99]-書中的相關討論)。可以使用測試優先開發方式(見第82頁習慣20),作為強制進行增量編程的方式。關鍵在于持續做一些細小而有用的事情,而不是做一段長時間的編程或重構。 這就是敏捷的方式。 在很短的編輯/構建/測試循環中編寫代碼。這要比花費長時間僅僅做編寫代碼的工作好得多。可以創建更加清晰、簡單、易于維護的代碼。 切身感受 在寫了幾行代碼之后,你會迫切地希望進行一次構建/測試循環。在沒有得到反饋時,你不想走得太遠。 平衡的藝術 □ 如果構建和測試循環花費的時間過長,你就不會希望經常運行它們了。要保證測試可以快速運行。 □ 在編譯和測試運行中,停下來想一想,并暫時遠離代碼細節,這是保證不會偏離正確方向的好辦法。 □ 要休息的話,就要好好休息。休息時請遠離鍵盤。 □ 要像重構你的代碼那樣,重構你的測試,而且要經常重構測試。 29.保持簡單“軟件是很復雜的東西。隨便哪個笨蛋都可以編寫出簡單、優雅的軟件。通過編寫史上最復雜的程序,你將會得到美譽和認可,更不用提保住你的工作了。” 也許你看過這樣一篇文章,其中提到了一個設計想法,表示為一個帶有花哨名稱的模式。放下雜志,眼前的代碼似乎馬上就可以用到這種模式。這時要捫心自問,是不是正的需要用它,以及它將如何幫你解決眼前的問題。問問自己,是不是特定的問題強迫你使用這個解決方案。不要讓自己被迫進行過分設計,也不要將代碼過分復雜化。 Andy曾經認識一個家伙,他對設計模式非常著迷,想把它們全都用起來。有一次,要寫一個大概幾百行代碼的程序。在被別人發現之前,他已經成功地將GoF那本書[FHJV95]中的17個模式,都運用到那可憐的程序中。 這不應該是編寫敏捷代碼的方式。 問題在于,許多開發人員傾向于將投入的努力與程序復雜性混同起來。如果你看到別人給出的解決方案,并評價說“非常簡單且易于理解”,很有可能你會讓設計者不高興。許多開發人員以自己程序的復雜性為榮,如果能聽到說:“Wow,這很難,一定是花了很多時間和精力才做出來的吧。”他們就會面帶自豪的微笑了。其實應當恰恰相反,開發人員更應該為自己能夠創建出一個簡單并且可用的設計而驕傲。 “簡單性”這個詞匯被人們大大誤解了(在軟件開 簡單不是簡陋 發工作以及人們的日常生活中,皆是如此)。它并 Simple is not simplistic 不意味著簡陋、業余或是能力不足。恰恰相反,相 比一個過分復雜、拙劣的解決方案,簡單的方案通常更難以獲得。 簡單性,在編程或是寫作中,就像是廚師的收汁調料。從大量的葡萄酒、主料和配料開始,你小心地進行烹調,到最后得到了最濃縮的精華部分。這就是好的代碼應該帶給人的感覺——不是一大鍋黏糊糊的、亂七八糟的東西,而是真正的、富含營養的、口味上佳的醬汁。 評價設計質量的最佳方式之一,就是聽從直覺。直覺不是魔術,它是經驗和技能的厚積薄發之產物。在查看一個設計時,聽從頭腦中的聲音。如果覺得什么地方不對,那就好好想想,是哪里出現了問題。一個好的設計會讓人覺得很舒服。 開發可以工作的、最簡單的解決方案。除非有不可辯駁的原因,否則不要使用模式、原則和高難度技術之類的東西。 切身感受 當你覺得所編寫的代碼中沒有一行是多余的,并且仍能交付全部的功能時,這種感覺就對了。這樣的代碼容易理解和改正。 平衡的藝術 □ 代碼幾乎總是可以得到進一步精煉,但是到了某個點之后,再做改進就不會帶來任何實質性的好處了。這時開發人員就該停下來,去做其他方面的工作了。 □ 要將目標牢記在心:簡單、可讀性高的代碼。強行讓代碼變得優雅與過早優化類似,同樣會產生惡劣的影響。 □ 當然,簡單的解決方案必須要滿足功能需求。為了簡單而在功能上妥協,這就是過分簡化了。 □ 太過簡潔不等于簡單,那樣無法達到溝通的目的。 □ 一個人認為簡單的東西,可能對另一個人就意味著復雜。 30.編寫內聚的代碼 “你要編寫一些新的代碼,首先要決定的就是把這些代碼放在什么地方。其實放在什么地方問題不大,你就趕緊開始吧,看看IDE中現在打開的是哪個類,直接加進去就是了。如果所有的代碼都在一個類或組件里面,要找起來是很方便的。” 內聚性用來評估一個組件(包、模塊或配件)中成員的功能相關性。內聚程度高,表明各個成員共同完成了一個功能特性或是一組功能特性。內聚程度低的話,表明各個成員提供的功能是互不相干的。 假定把所有的衣服都扔到一個抽屜里面。當需要找一雙襪子的時候,要翻遍里面所有的衣服——褲子、內衣、T恤等——才能找到。這很麻煩,特別是在趕時間的時候。現在,假定把所有的襪子都放在一個抽屜里面(而且是成雙放置的),全部的T恤放在另外一個抽屜中,其他衣服也分門別類。要找到一雙襪子,只要打開正確的抽屜就可以了。 與此類似,如何組織一個組件中的代碼,會對開發人員的生產力和全部代碼的可維護性產生重要影響。在決定創建一個類的時候,問問自己,這個類的功能是不是與組件中其他某個類的功能類似,而且功能緊密相關。這就是組件級的內聚性。 類也要遵循內聚性。如果一個類的方法和屬性共同完成了一個功能(或是一系列緊密相關的功能),這個類就是內聚的。 看看Charles Hess先生于1866年申請的專利,“可變換的鋼琴、睡椅和五斗柜”(見圖6-2)。 根據他的專利說明,他提供了“……附加的睡椅和五斗柜……以填滿鋼琴下未被使用的空間……”。接下來他說明了為什么要發明這個可變換的鋼琴。讀者可能已經見過類似這種發明的項目代碼結構了,而且也許其中有你的份。這個發明不具備任何內聚性,任何一個人都可以想象得到,要維護這個怪物(比如換墊子、調鋼琴等)會是多么困難。 看看最近的例子。Venkat曾經見過一個用ASP開發的、有20個頁面的Web應用。每個頁面都以HTML開頭,并包含大量VBScript腳本,其中還 了訪問數據庫的SQL語句。客戶當然會認為這個應用的開發已經失去了控制,并且無法維護。如果每個頁面都包括展示邏輯、業務邏輯和訪問數據看代碼,就有太多的東西都堆在一個地方了。 假定要對數據庫的表結構進行一次微調。這個微小的變化會導致應用中所有的頁面發生變化,而且每個頁面中都會有多處改變——這個應用很快就變成了一場災難。 如果應用使用了中間層對象(比如一個COM組件)來訪問數據庫,數據庫表結構更所造成的影響就可以控制在一定的范圍之內,代碼也更容易維護。 低內聚性的代碼會造成很嚴重的后果。假設有這樣一個類,實現了物種完全不想干的功能。如果這5個功能的需求或希捷發生了變化,這個類也必須跟著改變。如果一個(或者一個組件)變化的過于頻繁,這樣的改變會對整個系統形成“漣漪效應”,并導致更多的維護和成本的發生。考慮另一個只實現了一種功能的類,這個類變化的頻度就沒有那么高。類似的,一個更具內聚性的組件不會有太多導致其變化的原因,也因此而更加穩定。根據單一指責原則(查看《敏捷軟件開發:原則、模式與實踐》[Mar02]),一個模塊應該只有一個發生變化的原因。 一些設計技巧可以起到幫助作用。舉例來說,我們常常使用模型-視圖-控制器(MVC)模式來分離展示層邏輯、控制器和模型。這個模式非常有效,因為它可以讓開發人員獲得更高的內聚性。模型中的類包含一種功能,在控制器中的類包含另外的功能,而駛入中的類則只關心UI。 內聚性會影響一個組件的可重用性。組件粒度是在設計時要考慮的一個重要因素。根據重用發布等價原則([Mar02]):重用的粒度于發布的粒度相同。這就是說,程序庫用戶所需要的,是完整的程序庫,而不是其中的一部分。如果不能遵循這個原則,組件用戶就會被強迫只能使用所發布組件的一部分。很不幸的是,他們仍然會被不關心的那一部分的更新所影響。軟件包越大,可重用性就越差。 讓類的功能盡量集中,讓組件盡量小。要避免創建很大的類或組件,也不要創建無所不包的大雜燴類。 切身感受 感覺類和組件的功能都很集中:每個類或組件只做一件事,而且做得很好。Bug很容易跟蹤,代碼也易于修改,因為類和組件的責任都很清晰。 平衡的藝術 □ 有可能會把一些東西拆分成很多微小的部分,而使其失去了實用價值。當你需要一只襪子的時候,一盒棉線不能帶給你任何幫助。 □ 具有良好內聚性的代碼,可能會根據需求的變化,而成比例地進行變更。考慮一下,實現一個簡單的功能變化需要變更多少代碼。 31.告知,不要詢問 “不要相信其它的對象。畢竟,它們是有別人寫的,甚至有可能是你自己上個月頭腦發昏的時候寫的呢。從別人那里去拿你需要的信息,然后自己處理,自己決策。不要放棄控制別人的機會!” “面向過程的代碼取得信息,然后做出決策。面向對象的代碼讓別的對象去做事情。”Alec Sharp[Sha97]通過觀察后,一針見血地指出了這個關鍵點。但是這種說法并不僅限于面向對象的開發,任何敏捷的代碼都應該遵循這個方式。 作為某段代碼的調用者,開發人員絕對不應該基于被調用對象的狀態來做出任何決策,更不能去改變該對象的狀態。這樣的邏輯應該是被調用對象的責任,而不是你的。在該對象之外替它做決策,就違反了它的封裝原則,而且為bug提供了滋生的土壤。 David Bock使用“送報男孩和錢包的故事”很好地詮釋了這一點。假定送報男孩來到你的門前,要求付給他本周的報酬。你轉過身去,讓送報男孩從你的后屁股兜里掏出錢包,并且從中拿走兩美元(你希望是這么多),在把錢包放回去。然后,送報男孩就會開著他嶄新的美洲豹汽車揚長而去了。在這個過程中,送報男孩作為“調用者”,應該告訴 客戶付他兩美元。他不能探詢客戶的財務狀況,或是 將命令與查詢分離開來 錢包的薄厚,他也不能代替客戶做任何決策。這都是 Keep commands separate from queries 客戶的責任,而不屬于送報男孩。敏捷代碼也應該以 同樣的方式工作。 與告知,不要詢問相關的一個很有用的技術是:命令與查詢相分離模式(command-query separation)。就是要將功能和方法分為“命令”和“查詢”兩類,并在源碼中紀錄下來(這樣做可以幫助將所有的“命令”代碼放在一起,并將所有的“查詢”代碼放在一起)。 一個常規的“命令”可能會改變對象的狀態,而且有可能返回一些有用的值,以方便使用.一個“查詢”僅僅提供給開發人員對象的狀態,并不會對其外部的可見狀態進行修改. 這就是說,從外部看來,“查詢”不應該有任何副作用(如果需要的話,開發人員可能想在后臺做一些事先的計算或是緩存處理,但是取得對象中X的值,不應該改變Y的值). 像“命令”這種會產生內部影響的方法,強化了告知,不要詢問的建議.此外保證“查詢”沒有副作用,也是很好的編碼實踐,因為開發人員可以在單元測試中自由使它們,在不斷言或者調試器中調用它們,而不會改變應用的狀態. 從外部將“查詢”與“命令”隔離開來,還會給開發人員機會詢問自己為什么要暴露某些特定的數據.真的需要這么做嗎?調用者會如何使用它? 也許應該有一個相關的“名列”來替代它. 告知,不要詢問。不要搶別的對象或是組件的工作。告訴它做什么,然后盯著你自己的職責就好了。 切身感受Smalltalk使用“信息傳遞”的概念,而不是方法調用。告知,不要詢問感覺起來就像你的發送消息,而不是調用函數。 平衡的藝術 □ 一個對象,如果只有用作大量數據容器,這樣的做法很可疑,有些情況不會需要這樣的東芝,但并不像想象的那么頻繁。 □ 一個“命令”返回數據以方便使用是沒有問題的(如果需要的話,創建單獨讀取數據的方法也是可以的)。 □ 絕對不能允許一個看起來無辜的“查詢”去修改對象的狀態。 32.根據契約進行替換 “深沉次的繼承是很棒的。如果你需要其他類的函數,直接繼承它們就好了!不要擔心你創建的新類會造成破壞,你的調用者可以改變他們的代碼。這是他們的問題,而不是你的問題。” 保持系統靈活的關鍵方式,是當新代碼取代原有代碼之后,其他已有的代碼不會意思到任何差別。例如,某個開發人員可能想為通信的底層架構添加一種新的加密方式,或者使用同樣的接口實現更好的搜索算法。只要接口保持不變,開發人員就可以隨意修改實現代碼,而不是影響其他任何現有代碼。然而,說起來容易,做起來難。所以需要一點指導來幫助我們正確的實現。因此,去看看BarbaraLiskove的說法。 Liskov替換原則[Lis88]告訴我們:任何繼承之后得到派生類對象,必須可以替換任何被使用的基類對象,而且使用者不必知道任何差異。換句話說,某段代碼如果使用了基類中的方法,就必須能夠使用派生類的對象,并且自己不必進行任何修改。 這到底以為著什么?假定某個類中有一個簡單的方法,用來對一個字符串表進行排序,然后返回一個新的列表。并用如下的方式調用: Utils = new BasicUtils(); … sortedList = utils.sort(aList); 現在假定開發人員派生了一個BasicUtils的之類,并寫了一個新的sort()方法,使用了更快、更好的排序算法: Utils = new FasterUtils(); … sortedList = utils.sort(aList); 注意對象sort()的調用是完全一樣的,一個FasterUtils對象完美地替換了一個BasicUtils對象。調用utils.sort()的代碼可以處理任何類型的utils對象而且可以正常工作. 但如果開發人員派生了一個basicUtils的子類,并改變了排序的意義--也許返回的列表以相反的列表進行排列--那就嚴重違反了Liskov替換原則。 要遵守Liskov替換原則,相對基類的對應方法,派生類服務(方法)應該不要求更多,不承諾更少;要可以進行自由的替換。在設計類的繼承層次時,這是一個非常重要的考慮因素。 繼承是OO建模和編程中被濫用最多的概念之一。如果違反了Lisakov替換原則,繼承層次可能仍然可以提供代碼的可重用性,但是將會失去可擴展性。類繼承關系的使用者現在必須要檢查給定對象的類型,以確定如何針對其進行處理。當引入了新的類之后,調用代碼必須經常重新評估并修正。這不是敏捷的方式。 但是可以借用一些幫助。編譯器可以幫助開發人員強制執行Liskov 替換原則,至少在某種程度上是可以達到的。例如,針對方法的訪問修飾符。在java中重寫方法的訪問修飾符必須與被重寫方法的修改符相同,或者可訪問范圍更加寬大,也就是說如果基類方法是保護的,那么派生重寫方法的修飾符必須是保護的或者公共的。在C#和VB.NET中,被重寫方法與重寫方法的訪問保護范圍必須完全相同。 考慮一個帶有findLargest()方法的類Base,方法中拋出一個IndexOut-OfRangeException異常。基于文檔,類的使用者會準備抓住可能被拋出的異常。現在,假定你從Base類繼承得到類Derived,并重寫了findLargest()方法,在新的方法中拋出了一個不同的異常。現在如果某段代碼期待使用Base類對象,并調用了Derived類的實例,這段代碼就有可能接受到一個意想不到的異常。你的Derived類就不能替換使用到Base類的地方。在java中,通過不允許重寫方法拋出任何新的檢查異常避免了這個問題,除非異常本身派生自被重寫方法拋出的異常類(當然,對于像RuntimeException這樣的未檢查異常,編譯器就不能幫你了)。 不幸的是,java也違背了Liskov替換原則。Java.util.Stack類派生自java.util.Vector類。如果開發人員(不小心)將Stack對象發送給一個期待Vector實例的方法,Stack中的元素就可能被以與期望的行為不符的順序被插入活刪除。 當使用繼承時,要想想派生類是否可以替換基類。如果答案是 不能,就要問問自己為什么要使用繼承。如果答案是希望在編譯寫新類的時候,還要重用基類的代碼,也許要考慮轉而使用聚合,聚合是指在類中包含一個對象,并且該對象是其他類的實例,開發人員將責任委托給所包含的對象來完成(該技術同樣被稱為委托)。 圖6-3中展示了委托與繼承之間的差異。在圖中,一個調用者調用了CalledClass中的MethodA(),而它將會通過繼承直接調用Base Class中的說法。在委托的模型中,Called Class必須要顯式地將方法調用轉向包含的委托方法。 Base Class methodA() Dekegate ClassmethodA() Called Class methodA()à1Called Class繼承 委托圖6-3 委托與繼承 那么繼承和委托分別在什么時候使用呢? □ 如果新類可以替換已有的類,并且它們之間的關系可以通過is-a來描述,就要使用繼承 □ 如果新類只是使用已有的類,并且二者之間的關系可以描述為has-a或者user-a就使用委托吧。 開發人員可能會爭辯說,在使用委托時,必須要寫很多小方法,來將方法調用指向所包含的對象。在繼承中,不需要這樣做,因為基類中的公共方法在派生類中就已經是可用的了,僅憑這一點,并不能構成使用繼承足夠好的理由。 你可以在開發一個好的腳本或是好的IDE宏,來幫助編寫這幾行代碼,或者使用一種更好的編程語言/環境,以支持更自動化形式的委托(比如Ruby這一點就做的不錯了)。 通過替換代碼來擴展系統。通過替換遵循接口契約的來,來添加并改進功能特性。要多使用委托而不是繼承。 切身感受 這會讓人覺得有點鬼鬼祟祟的,你可以偷偷地替換組件代碼到代碼庫中,而且其他代碼對比此一無所知,它們還獲得了新的或改進后的功能。 平衡藝術 □ 相對繼承來說,委托更加靈活,適應力也更強。 □ 繼承不是魔鬼,只是長久以來被大家誤解了。 □ 如果你不確定一個接口做出了什么樣的承諾,或是有什么樣的需求,那就很難提供一個對其有意義的實現了。 第7章、敏捷調試 你也許會對木匠那毫無差錯的工作印象深刻,但我向你保證,事實不是這樣的,真正的高手只是知道如何亡羊補牢。--Jeff Miller,家具制造者,作家 即使是運作得最后的敏捷項目,也會發生錯誤。Bug、錯、缺陷—不管被稱作什么,它們總會發生。 在調試時面對的真正問題,是無法用固定的時間來限制。可以規定設計會議的持續時間,并在時間截止時采用最佳的方案。但是調試所耗費的時間,可能是一個小時、一天,甚至一周過去了,還是沒有辦法找到并解決問題。 對于一個項目來說,這樣沒有準備把握的時間消耗是不可接受。不過,我們可以使用一些輔助技術,涵蓋的范圍包括:保留以前的問題解決方案,以及提供發生問題時的更多有用細節。 想要更加有效的重用你的知識和努力,記錄問題解決日志是很有用的,我們會在下一頁看到如何具體操作。當編譯器警告有問題的時候,要假定警告就是錯誤,并且馬上把它們解決掉(第132頁)。 想在一個完整的系統中跟蹤問題非常困難—甚至是不可能的。如果可以對問題各個擊破,正如我們在第136頁中看到的那樣,就更容易找到問題了,不同與某些欲蓋彌彰的行為,應報告所有的異常,如第139頁所述。最后,在報告某些事情出錯之時,必須要考慮用戶的感受,并且提供有用的錯誤信息。我們會在第141頁看到這是為什么。 33 記錄問題解決日志“在開發過程中是不是經常遇到似曾相識的問題?這沒關系。以前解決過的問題,現在還是可以解決掉的。” 面對問題(并解決它們)是開發人員的一種生活方式。當問題發生時,我們希望趕緊把它解決掉。如果一個熟悉的問題再次發生,我們會希望記起第一次是如何解決的,而且下午下次能夠更快地把它搞定。然而,有時一個問題看起來跟以前遇到的完全一樣,但是我們卻不記得是如何修復的了。這種狀況時常發生。 不能通過Wed搜索獲得答案嗎?畢竟互聯網已經成長為如此令人難以置信的信息來源,我們也應該好好加以利用。從Wed上尋找答案當然勝過僅靠個人努力解決問題。可這是非常耗費時間的過程。有時可以找到需要的答案,有時除了找到一大堆意見和建議之外,發現不了實質性的解決方案。看到有多少開發人員遇到同樣的問題,也許會感覺不錯,但我們需要的是一個解決辦法。 想要得到更好的效果,不妨維護一個保存曾遇到的問題以及對應解決方案的日志。這樣,當問題發生時,就不必說:“嘿,我曾碰到過這個問題, 但是不記得是怎么解決的了。”可以快速搜索以前用 不要在同一處跌倒兩次 過的方法。工程師們已經使用這種方式很多年來, Don’t get burned twice 他們稱之為每日日志(daylog)可以選擇符合要求的任何格式。下面這些條目可能會用得上。 □ 問題發生日期。 □ 問題簡述。 □ 解決方案詳細描述。 □ 引用文章后網址,以提供更多細節或相關信息。 □ 任何代碼片段、設置后對話框的截屏,只要它們是解決方案的一部分,或者可以幫助更深入地理解相關細節。 要將日志保存為可供計算機搜索的格式,就可以進行關鍵字搜索以快速查找細節。圖7-1展示了一個簡單的例子,其中帶有超鏈接以提供更多信息。圖7-1 帶有超鏈接的解決方案條目示例 如果面臨的問題無法在日志中找到解決方案,在問題解決之后,要記得馬上將新的細節記錄到日志中去。 要共享日志給其他人,而不僅僅是靠一個維護。把它放到共享的網絡驅動器中,這樣其他人也可以使用。或者創建一個Wiki,并鼓勵其他開發人員使用和更新其內容。 維護一個問題及其解決方案的日志。保留解決方案是修復問題過程的一部分,以后發生相同或類似問題時,就可以很快找到并使用了。 切實感受 解決方案日志應該作為思考的一個來源,可以在其中發現某些特定問題的細節,對于某些類似但是有差異的問題,也能從中獲得修復的指引。 平衡的藝術□ 記錄問題的時間不能超過在解決問題上花費的時間。要保持輕量級和簡單,不必達到對外發布式的質量。□ 找到以前的解決方法非常關鍵。使用足夠的關鍵字,可以幫助你在需要的時候發現需要的條目。□ 如果通過搜索Wed,發現沒人曾經遇到同樣的問題,也許搜索的方式有問題。□ 要記錄發生問題時應用程序、應用框架或平臺的特定版本。同樣的問題在不同的平臺或版本上可能表現得不同。 □ 要記錄團隊做出一個重要決策的原因。否則,在6~9個月之后,想在重新回顧決策過程的時候,這些細節就很難在記得了,很容易發生互相指責的情形。 34 警告就是錯誤 “編譯器的警告信息只不過是給過分小心和過于書呆子氣的人看的。他們只是警告而已。如果導致的后果很嚴重,它們就是錯誤了,而且會導致無法通過編譯,所以干脆忽略它們就是了。” 當程序中出現一個編譯錯誤時,編譯器或是構建工具會拒絕產生可執行文件,我們別無選擇——必須要先修正錯誤,在繼續前行。 然而,警告卻是另外一種狀況。即使代碼編譯時產生了警告,我們還是可以運行程序。那么忽略警告信息繼續開發代碼,會導致什么狀況呢?這樣做等于是坐在了一個嘀嗒作響的定時炸彈上,而且它很有可能在最糟糕的時刻爆炸。 有些警告是過于挑剔的編譯器的良性副產品,有些則不是。例如:一個關于未被使用的變量的警告,可能不會產生什么惡劣影響,但卻有可能是暗示某些變量被錯誤使用了。 最近在一家客戶那里,Venkat發現一個開發中的應用有多于300個警告。其中一個被開發人員忽略的警告是這樣: Assignment in conditional expression is always constant; Did you mean to use == instead of = ? 條件的表達式中賦值為常量,你是否要使用==而不是=? 相關的代碼如下: If (theTextBox.Visible = true)… 也就是說,If語句總是會評估為true,無論不幸的theTextBox變量是什么狀況。看到類似這樣真正的錯誤被當作警告忽略掉,真是令人感到害怕。 看看下面的C#代碼: Public class Base{Public virtual void foo(){ Console.WriteLine(“Base.foo”);}} Public class Derived :Base{Public vitrual viod foo(){Console.WriteLine(“Derived.foo”);}}Class Test{Static void Main(string[] args){Derived d = new Derived();Base b = d;d.foo();b.foo();}}在使用Visual Studio 2003默認的項目設置對其進行編譯時,會看到如此信息“構建 1個成功,0失敗,0跳過”顯示在Output窗口的底部。運行程序,會得到這樣的輸出: Derived.foo Base.foo 但這不是我們預期的結果。應該看到兩次對Derived類中foo方法的調用。是哪里出錯了?如果仔細查看Ouput窗口,可以發現這樣的警告信息: Warning.Derived.foo hides inherited member Base.foo To make the current member overrode that implementation, Add the override keyword.Otherwise 、 you’ d add the new keyword. 這明顯是一個錯誤——在Derived類的foo()方法中,應該使用override而不是virtual。 想象一下,有組織地忽略代碼中類似這樣的錯誤會導致什么樣的后果。代碼的行為會變得無法預測,其質量會直線下降。 可能有人會說優秀的單元測試可以發現這些問題。是的,它們可以起到幫助作用(而且也應該使用優秀的單元測試)。可如果編譯器可以發現這種問題,那為什么不利用它呢? 這可以節省大量的時間和麻煩。 要找到一種方式讓編譯器將警告信息作為錯誤提示出來。如果編譯器允許調整警告的報告級別,那就把級別調到最高,讓任何警告不能被忽略。例如:GCC編譯器支持-Werror參數,在Visual Studio中,開發人員可以改變項目的設置,將警告視為錯誤。 對于一個項目的警告信息來說,至少也要做到這種地步。然而,如果采取這種方式,就要創建每個項目去進行設置。如果可以盡量以全局化的方式來進行設置就好了。 比如,在Visual Studio中,開發人員可以修改項目模板(查看.NET Gotchas[Sub05]獲取更多的細節),這樣在計算機上創建的任何項目,都會有同樣的完整項目設置。在當前版本Eclipse中,可以按照這樣的順序修改設置:Windows—Preferences-java-Compiler-Errors/Warnings。如果使用其他的語言或IDE,花一些時間來找出如何在其中將警告作為錯誤處理吧。 在修改設置的時候,要記得在構建服務器上使用的持續集成工具中,修改同樣的設置選項。(要詳細的了解持續集成,查看第87頁習慣21。)這個小小的設置,可以提升團隊簽入到源碼控制系統中的代碼質量。 在開始一個項目的時候,要把相關的設置都準備好。在項目進行到一半的時候,突然改變警告設置,有可能會帶來顛覆性的后果,導致難以控制。 編譯器可以輕易處理警告信息,可是你不能。 警告給人的感覺就像……哦,警告。他們就某些問題給出警告,來吸引開發人員的注意。 平衡的藝術 □ 雖然這里探討的主要是編譯語言,解釋型語言通常也有標志,允許運行時警告。使用相關標志,然后捕獲輸出,以識別并最終消除警告。 □ 由于編譯器的bug或第三方工具或代碼的原因,有些警告無法消除。如果確實沒有應對之策的話,就不要在浪費更多的時間了。但是類似的狀況很少發生。 □ 應該經常指示編譯器:要特別注意別將無法避免的警告作為錯誤進行提示,這樣就不用費力去查看所有的提示,以找到真正的錯誤和警告。 □ 棄用的方法被棄用是有原因的。不要再使用它們了。至少,安排一個迭代器來將它們(以及它們引起的警告信息)安全地移除掉。 □ 如果將過去開發完成的方法標記為棄用方法,要記錄當前用戶應該采取何種變通之策,以及被棄用的方法將會在何時一起移除

?

總結

以上是生活随笔為你收集整理的高效程序员的45个习惯的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

日韩色爱| 免费观看mv大片高清 | 天堂av色婷婷一区二区三区 | 久久午夜电影网 | 九九免费在线视频 | 夜夜躁狠狠燥 | av无限看| 久久久国产精品人人片99精片欧美一 | 国产免费观看高清完整版 | 久久久久久看片 | 成人毛片100免费观看 | 超碰人人草 | 综合视频在线 | 国产精品精品国产色婷婷 | 久久精品视频国产 | 国产高清av免费在线观看 | 成人一级电影在线观看 | 国产区精品区 | 91天堂素人约啪 | 私人av| 国产精品免费一区二区三区在线观看 | 久久久99精品免费观看app | 九9热这里真品2 | 四虎国产精品成人免费4hu | 天天干中文字幕 | 国精产品999国精产 久久久久 | 婷婷四房综合激情五月 | 亚洲国产欧洲综合997久久, | 99国内精品| 日日激情| 国产在线日韩 | 亚州精品天堂中文字幕 | 日韩电影在线看 | av久久在线 | 岛国一区在线 | a国产精品 | a黄色| 99热精品免费观看 | 国外调教视频网站 | 欧美黑人猛交 | 在线视频99 | 91麻豆精品国产91久久久更新时间 | 综合久久久久久 | 又紧又大又爽精品一区二区 | 久久97久久97精品免视看 | 99精品欧美一区二区三区黑人哦 | 在线观看黄色 | 欧美在线观看视频一区二区 | 日本精品久久久一区二区三区 | 久久超 | 青青河边草免费观看 | 天天天干| 欧美日韩一区二区三区不卡 | 在线小视频你懂的 | 天天射天天干 | 成人黄色大片 | 人成电影网| 久久久久久激情 | www.国产在线 | www.99热精品 | 91久久精品一区二区二区 | av免费电影在线 | 亚洲精品视频偷拍 | 国产九色在线播放九色 | 精品视频在线观看 | 国产精品久久久久久高潮 | 蜜桃av人人夜夜澡人人爽 | 青春草免费在线视频 | 国产中文字幕在线 | www.夜夜| 日本亚洲国产 | 毛片基地黄久久久久久天堂 | 96精品视频 | 亚洲精品在 | 日本狠狠干 | 亚洲激情国产精品 | 亚洲国产久| 91成版人在线观看入口 | 天天操比| 五月天婷婷丁香花 | 在线观看中文字幕一区二区 | 久久视频99 | 日本中文字幕电影在线免费观看 | 99福利影院 | 中文视频在线 | 国产精品18久久久久久首页狼 | 人人网av | 国产精品毛片一区二区 | www日韩精品 | 五月婷婷在线视频 | 6080yy午夜一二三区久久 | 久久高清视频免费 | 国产视频一区精品 | 最新成人在线 | a成人v在线 | 一级黄色电影网站 | 久久国产麻豆 | 日韩字幕| 久久综合九色综合久久久精品综合 | 免费黄色在线 | 亚洲 欧美 日韩 综合 | 国产91丝袜在线播放动漫 | 蜜臀精品久久久久久蜜臀 | 成年人黄色免费看 | 日韩一级电影网站 | 婷婷 综合 色 | 91麻豆精品国产91久久久久久久久 | 国产69久久久欧美一级 | 九九免费精品视频 | 精品国产一区二区三区四 | 亚洲激情一区二区三区 | 黄色1级毛片 | 国产精品久久久av | 国产视频第二页 | 久久精品国产一区二区 | 91麻豆精品一区二区三区 | 超碰在线日本 | 中文字幕在线久一本久 | 一区二精品 | 蜜臀aⅴ国产精品久久久国产 | 久久久久成人精品 | 99久久这里只有精品 | 免费av网站在线看 | 亚洲精品国偷拍自产在线观看蜜桃 | 久久久久综合 | 六月天色婷婷 | 亚洲视频1区2区 | 探花视频免费在线观看 | 在线观看视频三级 | 伊人五月天综合 | 中文字幕成人网 | 久久久国产一区二区三区四区小说 | 国产精品美女毛片真酒店 | 国产综合精品一区二区三区 | 成人国产精品 | 91色在线观看 | 亚洲成a人片综合在线 | 免费高清在线观看成人 | 亚洲好视频 | 在线韩国电影免费观影完整版 | 国产精品热 | 国产一及片 | 91丨九色丨91啦蝌蚪老版 | 99精品国产aⅴ | 国内精品久久久久影院优 | 中国一级片在线观看 | 亚洲欧洲国产精品 | 91九色网站 | 国产在线91在线电影 | 91在线免费看片 | 国产精品va在线观看入 | 日韩成人黄色av | 亚洲一区二区视频 | 夜夜婷婷| 日韩免费观看一区二区 | 亚洲在线免费视频 | 日韩专区中文字幕 | 制服丝袜天堂 | 三上悠亚一区二区在线观看 | 91视频在线观看免费 | 久久综合成人网 | 日韩一区二区三区在线看 | 欧美精品一区二区在线观看 | 欧美日韩国产综合网 | 久草国产视频 | 五月花丁香婷婷 | 国内一级片在线观看 | h动漫中文字幕 | 亚洲精品视频在 | 国产中文字幕视频在线观看 | 中文字幕在线观看资源 | 亚洲国产字幕 | 9999在线| av片免费播放 | 欧美久久久久 | 亚洲国产成人在线 | 国产91精品看黄网站在线观看动漫 | 97碰碰精品嫩模在线播放 | 亚洲日日夜夜 | 亚洲草视频 | 亚洲三级毛片 | 91一区二区三区在线观看 | 国产色视频网站 | 在线国产视频 | 天天激情在线 | 五月天国产 | 四虎在线观看精品视频 | 成全在线视频免费观看 | 免费十分钟 | 久久手机精品视频 | 在线不卡视频 | 成人av免费播放 | 97超碰成人在线 | 国产亚洲精品美女久久 | 免费看黄的 | 欧美性春潮 | 国产污视频在线观看 | 综合伊人av | 天天射天天添 | 五月激情综合婷婷 | 97精品超碰一区二区三区 | 国产亚洲精品综合一区91 | 国产精品成人自产拍在线观看 | 人人狠狠综合久久亚洲婷 | 欧美在线观看禁18 | 久久黄色精品视频 | 91在线看片 | 成人97人人超碰人人99 | 久久国产成人午夜av影院宅 | 亚洲精品99久久久久中文字幕 | 人人爽人人爽 | 91在线一区| 久久露脸国产精品 | 久久精品激情 | 久久久久久麻豆 | 91最新视频 | 最新av免费在线观看 | 亚洲国产视频直播 | 日韩色中色 | 国产精品自产拍在线观看桃花 | 久久国产视频网站 | 四虎成人精品永久免费av | 黄色99视频 | 免费久草视频 | 四虎免费在线观看视频 | 成人久久18免费网站麻豆 | 四虎影视成人精品国库在线观看 | 婷婷久操 | 亚洲一区免费在线 | 最近更新好看的中文字幕 | 亚洲麻豆精品 | 国产日韩精品久久 | www操操 | 综合色影院 | 亚洲免费国产 | av在线观| 中日韩三级视频 | 国产视频久久久 | 国产区精品视频 | 99热精品久久 | 精品在线观看一区二区 | 色婷婷99 | 在线导航av | 超碰人人91 | 国产精品欧美久久久久久 | 日韩一级片观看 | 日韩在线视频观看免费 | 99热最新网址 | 日本精品久久久久影院 | 99久久精品网 | 欧美日韩高清免费 | 久久情爱 | 一区二区三区动漫 | 日韩在线视频观看 | 三级黄色在线观看 | 最近字幕在线观看第一季 | 91精品国产成人观看 | 91三级视频 | 国产精品一区二区免费 | 亚洲成人二区 | 免费看片日韩 | 日韩91在线 | 99久在线精品99re8热视频 | 99热99热 | 日韩91av| 日韩狠狠操 | 国产96在线 | 日韩精品免费一区二区三区 | 毛片网免费 | 国产成人精品999 | 日韩视频中文 | av在线播放网址 | 美女视频永久黄网站免费观看国产 | 五月婷婷综合久久 | 伊人日日干 | 国产精品私拍 | 人人爽人人爱 | 午夜免费视频网站 | 中文字幕一二三区 | 久久久久久看片 | 99免费看片| 免费日韩高清 | 国产成人精品久久亚洲高清不卡 | 日韩中文字幕a | 一区二区三区在线观看免费 | 久草视频在线看 | 欧美日韩视频一区二区三区 | 午夜av免费看 | 超碰电影在线观看 | 婷婷久久一区 | 中午字幕在线 | 国产一级91 | 国产一级大片在线观看 | 国产欧美高清 | 午夜视频在线网站 | 五月天狠狠操 | 亚洲四虎 | 亚洲专区免费观看 | 国产传媒一区在线 | 亚洲成a人片综合在线 | 青青河边草免费 | 久久视频一区二区 | 色婷婷视频在线观看 | 午夜精品久久久久久久99热影院 | 狠狠操综合网 | 97av超碰 | 在线观看成人毛片 | 日韩欧美精品一区二区三区经典 | 久久国产亚洲精品 | 婷婷色中文| aa级黄色大片 | 久久66热这里只有精品 | 日韩久久久久久久久 | 免费看网站在线 | 欧美精品久久久久久 | 欧美午夜精品久久久久久浪潮 | 成年人电影免费在线观看 | 伊人天天综合 | 成人av手机在线 | 国产精品日韩欧美 | 中国一级片视频 | 国产小视频免费在线网址 | 激情综合五月婷婷 | 精品一区二区久久久久久久网站 | 久久免费视频网站 | 久香蕉| 人人爽人人爽人人片av免 | 亚洲三级影院 | 丁香花在线观看免费完整版视频 | 国产亚洲精品久久久久久电影 | 免费看黄在线看 | 亚洲免费不卡 | 蜜臀av性久久久久av蜜臀三区 | 国产精品热 | 国产一级在线观看视频 | 成人在线观看免费 | 亚洲经典精品 | 黄色网址国产 | 天堂av在线网 | 蜜臀av网址| 99精品国产aⅴ | 免费看黄色毛片 | av中文字幕在线观看网站 | 亚洲成成品网站 | 一区二区三区国产精品 | 亚洲丝袜一区 | 日韩视频在线一区 | 国产999精品久久久久久绿帽 | 欧洲精品久久久久毛片完整版 | www.色午夜 | 中文av影院 | 亚洲好视频 | 日日夜夜爱 | 99久久综合精品五月天 | 青青草久草在线 | 久久久国际精品 | 中文字幕av免费在线观看 | 国产破处在线播放 | 日韩不卡高清视频 | av中文字幕在线观看网站 | 免费看毛片在线 | 免费在线看成人av | 国产精品永久免费视频 | 久久久免费精品国产一区二区 | 国产精品2019 | 国产三级香港三韩国三级 | 丁香六月欧美 | 97国产精品亚洲精品 | 亚洲午夜久久久久久久久久久 | 一区二区国产精品 | 国产91免费在线 | 九九精品视频在线看 | 亚洲午夜大片 | 亚洲mv大片欧洲mv大片免费 | 中日韩免费视频 | 91免费高清视频 | 久久久久久久久久久影院 | 日本精品午夜 | 久久99国产精品免费 | 日韩肉感妇bbwbbwbbw | 国产成人精品av在线观 | 国产一卡二卡在线 | 在线播放 亚洲 | 欧美一区日韩精品 | 91福利专区 | 免费碰碰| 色资源网在线观看 | 日韩在线观看视频一区二区三区 | 亚洲欧洲av| 一区二区伦理 | 欧美激情综合五月色丁香 | 免费观看性生交 | 国产精品精品 | 日韩在线网址 | 91精品啪在线观看国产线免费 | 国产精品永久 | 涩涩资源网| 91精品国产自产在线观看 | 91av在线免费播放 | 久久久久免费精品视频 | 国产只有精品 | 国产资源在线播放 | 97韩国电影 | 国产69精品久久久久99尤 | 国产精品日韩欧美一区二区 | 91经典在线| 免费a级黄色毛片 | 丁香视频五月 | 一级黄色免费网站 | 福利av影院 | 综合在线亚洲 | 久久,天天综合 | 久久久91精品国产一区二区精品 | 青青河边草免费直播 | 一级理论片在线观看 | 天天色天天射天天综合网 | 麻豆视频免费入口 | 久久精品一区二区三区国产主播 | 久久免费看毛片 | 精品黄色视 | 亚洲春色综合另类校园电影 | 久久情侣偷拍 | 狠狠干夜夜操天天爽 | 国产一级久久 | 亚洲一区二区观看 | 99久免费精品视频在线观看 | 波多野结依在线观看 | 国产精品午夜在线观看 | 日本爱爱片 | 免费观看全黄做爰大片国产 | 久久激情五月丁香伊人 | 午夜精品久久久久久久爽 | 精品高清视频 | 99热超碰 | 国产裸体永久免费视频网站 | 日本精品久久久一区二区三区 | 日韩精品第一区 | av福利超碰网站 | 亚洲乱码久久久 | 亚洲精品久久久久中文字幕二区 | 国产主播99| 亚洲精品h | 国产成人久久av免费高清密臂 | 国产精品自产拍在线观看 | 又黄又刺激又爽的视频 | 操老逼免费视频 | av成人免费网站 | 天天爽夜夜爽精品视频婷婷 | 日本不卡一区二区三区在线观看 | 精品一区二区三区在线播放 | 超碰国产在线播放 | 黄色av电影免费观看 | 久久久久综合精品福利啪啪 | 有码中文字幕在线观看 | 亚洲精品男人天堂 | 成在线播放 | 亚洲精品高清视频 | 亚洲精品玖玖玖av在线看 | 911av视频| 色欲综合视频天天天 | 久久最新视频 | 成人福利在线观看 | 亚洲欧美视频 | 国产999在线| 热久久电影 | 在线一区电影 | 国产精品视频永久免费播放 | 午夜黄网 | 久久综合一本 | 日韩精选在线观看 | 亚洲欧洲中文日韩久久av乱码 | 日日婷婷夜日日天干 | 人人爱人人射 | 久草在线视频精品 | 久久久久一区 | 色婷婷啪啪免费在线电影观看 | 91视频啊啊啊| 91看片在线免费观看 | 国产视频亚洲视频 | 激情 一区二区 | 亚洲五月婷| 亚洲精品天天 | 国产精品青草综合久久久久99 | 在线黄网站 | 日韩亚洲在线观看 | 一区二区三区不卡在线 | 91日韩免费 | 91网址在线观看 | 午夜av色 | 最近高清中文字幕在线国语5 | 午夜精品久久久99热福利 | 欧美精品做受xxx性少妇 | 国产成人亚洲在线观看 | 色网站视频 | 中文字幕人成不卡一区 | 国产精品成人国产乱 | 狠狠干美女| 国产传媒中文字幕 | 亚洲精品视频在线免费 | 成人在线视频免费看 | 色婷婷国产 | 国内精品中文字幕 | 91视频国产高清 | 看片在线亚洲 | 久久综合狠狠综合 | 天天曰视频 | 久久9999久久 | 国产精品一区二区三区四区在线观看 | 久久中文精品视频 | 天天亚洲综合 | 国产精品入口传媒 | 一级性视频 | 久久精品国产第一区二区三区 | 免费观看www小视频的软件 | 国内精品久久久久影院优 | 久久不射影院 | 天天射天天操天天色 | 色综合天天色综合 | 久久高视频 | 人人澡人人爱 | 久久与婷婷 | 一级黄毛片| 亚洲日b视频 | 久草视频免费看 | 天天综合狠狠精品 | 久久综合加勒比 | 丁香高清视频在线看看 | 在线观看视频中文字幕 | 99免费精品 | 久草视频在线资源站 | 日本性高潮视频 | 日韩中文字幕a | 亚洲精品永久免费视频 | 91久久久国产精品 | 97视频播放 | 毛片久久久 | 久久狠狠亚洲综合 | 亚洲国产欧美在线人成大黄瓜 | 久久久久久久久久久久99 | 免费人成网ww44kk44 | 亚洲欧美视频在线播放 | a一片一级 | 天天综合网 天天综合色 | 久草在线高清视频 | 在线最新av | 国产成人一区二区三区在线观看 | 欧美激情片在线观看 | 91社区国产高清 | 国产日韩中文在线 | 日本三级人妇 | 午夜在线免费观看 | 999久久 | 国产精品一级视频 | 九9热这里真品2 | 久久99精品久久久久蜜臀 | 99精品乱码国产在线观看 | 天天爽天天搞 | 免费十分钟 | 国产成人久久精品亚洲 | 伊人色综合久久天天网 | 中文字幕视频播放 | 五月天高清欧美mv | 国内精品视频在线 | 成人资源网 | 国产精品久久久久久久久久免费看 | 四虎国产视频 | 成人黄色小说在线观看 | 色综合激情网 | 国产成人中文字幕 | 丁香六月久久综合狠狠色 | 国产精品毛片一区视频播 | 蜜桃视频色 | 在线观看日韩视频 | 国产成人av片 | 久久免费毛片视频 | 亚洲国产黄色片 | 国产精品18久久久久久不卡孕妇 | 久久99精品国产 | 在线观看视频在线观看 | 黄色1级大片 | 91在线视频在线 | 午夜视频在线瓜伦 | 黄色小说网站在线 | 国产黄色美女 | 999国产 | 国产区 在线 | h动漫中文字幕 | 国产伦理精品一区二区 | 性色av免费观看 | 亚洲免费高清视频 | 亚洲精品国精品久久99热 | 欧美成人aa | 美女黄频在线观看 | 视频在线观看亚洲 | 精品久久九九 | 国产视频在线观看一区二区 | 超碰人人在 | 免费高清在线观看成人 | 国产资源在线播放 | 中文在线免费一区三区 | 99精品国产视频 | 久久久国产一区二区三区 | 91精品在线视频观看 | 97在线公开视频 | 亚洲精品国产高清 | 久久久免费毛片 | 成年人免费电影在线观看 | 在线国产小视频 | 丝袜制服天堂 | a级黄色片视频 | 精品国产区 | 综合网天天 | 欧美一级片播放 | 天天干天天操天天入 | 欧美激情综合五月色丁香 | 一区二区伦理 | 黄色大片入口 | 在线91色| 国产伦精品一区二区三区四区视频 | 激情婷婷色 | www.色午夜.com | 国产91粉嫩白浆在线观看 | 中文字幕在线人 | 日日夜夜操操 | 在线v片| 九九九电影免费看 | 久久久穴 | 国产剧在线观看片 | 天天干天天干天天干 | 色伊人网 | 日日操日日操 | 伊人伊成久久人综合网站 | 久久999精品 | 日韩成人免费在线观看 | 日韩精品一区二区免费视频 | 69av网| www黄色 | 99热 精品在线 | 天天在线免费视频 | 亚洲成人二区 | 亚洲精品视频在线看 | 91视频传媒 | 在线观看黄色av | 久99久中文字幕在线 | 天天天天色射综合 | 久草色在线观看 | 久久av福利 | 色5月婷婷| 亚洲精品小视频在线观看 | 色视频在线免费观看 | 中文在线a√在线 | 91香蕉视频在线下载 | 亚洲午夜精品久久久久久久久久久久 | 91传媒激情理伦片 | 色综合色综合久久综合频道88 | 日韩精品专区在线影院重磅 | 久久亚洲福利视频 | 五月天天色 | 波多野结衣精品 | 国产精品永久免费视频 | www.夜夜操.com | 国产精品v欧美精品v日韩 | 色黄久久久久久 | 成人国产精品av | 国产乱对白刺激视频在线观看女王 | 成人免费在线视频 | 亚洲欧美成aⅴ人在线观看 四虎在线观看 | 日本性xxx | 在线观看的a站 | www.色在线| 国产精品黄色av | 日韩欧美视频在线播放 | 最近中文字幕 | 免费国产黄线在线观看视频 | 欧美天堂影院 | 亚洲精品视频在线观看视频 | 国产精品无 | 天天色.com | 99re久久资源最新地址 | 婷婷丁香视频 | 日本性久久 | 久久99国产精品久久99 | 亚洲成av人片在线观看香蕉 | 久久久久久国产精品 | 成人免费亚洲 | 黄色亚洲大片免费在线观看 | 欧美黄色成人 | 久久精品亚洲综合专区 | 在线观看av片| 三级黄免费看 | 国产一区免费看 | 丁香婷婷深情五月亚洲 | 一级性视频 | 免费在线国产精品 | 亚洲aⅴ久久精品 | 日韩精品中文字幕在线观看 | 国产.精品.日韩.另类.中文.在线.播放 | 日韩极品在线 | 国产在线精品区 | 美女网站在线观看 | 免费在线观看亚洲视频 | 亚洲精品乱码久久久久久 | 99色免费| 午夜骚影| 久草影视在线观看 | 久久1电影院 | 最近日韩免费视频 | 久久久18 | 有码一区二区三区 | 日本一区二区三区免费观看 | 青青久草在线 | 在线视频福利 | 日韩在线小视频 | 又污又黄的网站 | 欧美国产视频在线 | 成人免费共享视频 | 国产又粗又猛又爽又黄的视频免费 | 久久艹在线观看 | 国产香蕉视频 | 久精品一区 | 国产91精品看黄网站在线观看动漫 | 天天操夜夜操 | 久久综合激情 | 91毛片在线观看 | 麻豆国产在线视频 | 欧美亚洲xxx | 国产手机av | 国产精品视频免费在线观看 | 欧美二区三区91 | 国产免费亚洲 | 日韩欧美视频在线观看免费 | 国产99久久久精品视频 | 天天操天天干天天爽 | 日韩欧美精品在线 | 欧美一级片在线播放 | 免费在线观看av网址 | 欧美激情精品久久久久久免费印度 | 看毛片网站| 91xav| 久久国精品 | 欧美日韩国产在线观看 | 高清不卡一区二区在线 | 午夜久久电影网 | 久久久久这里只有精品 | 国产精品一区二区三区视频免费 | 亚洲精区二区三区四区麻豆 | 蜜臀精品久久久久久蜜臀 | 五月天视频网站 | 国产电影黄色av | 国产黄色精品在线 | 国产中文字幕在线播放 | 亚洲mv大片欧洲mv大片免费 | 丁香五月亚洲综合在线 | 久久最新网址 | 九色精品免费永久在线 | 久久9999久久免费精品国产 | 日韩在线视频不卡 | 久久国产手机看片 | 911亚洲精品第一 | 黄色软件在线观看 | 日韩精品久久中文字幕 | 亚洲伊人色 | 久久视频国产 | 国产成人一区二区三区久久精品 | 黄色免费大全 | 亚洲精品黄网站 | 在线免费观看的av网站 | 97免费| 99久久精品免费看国产 | 成人精品一区二区三区中文字幕 | 久久免费视频在线观看 | 国产精品午夜免费福利视频 | 亚洲综合色站 | 国产精品12 | 在线激情网 | 99久久久久 | 中午字幕在线 | 国产在线播放一区二区 | 干狠狠 | a级国产乱理伦片在线播放 久久久久国产精品一区 | 欧美 日韩 国产 成人 在线 | 一区二区三区中文字幕在线观看 | 色婷婷一 | 国产日韩精品一区二区三区在线 | 精品乱码一区二区三四区 | 国产韩国日本高清视频 | 射九九| 五月开心六月伊人色婷婷 | 日韩在线观看第一页 | 色av男人的天堂免费在线 | 国产99久久久精品视频 | 一区二区三区四区在线免费观看 | 99久久激情视频 | 中文字幕日韩伦理 | 国产资源av| 国产18精品乱码免费看 | 天天躁日日 | 精品免费在线视频 | 精品视频成人 | 日本资源中文字幕在线 | 国产精品手机在线 | 婷婷丁香在线视频 | 欧美成人中文字幕 | 精品高清美女精品国产区 | 91麻豆看国产在线紧急地址 | 天天综合天天综合 | a在线观看免费视频 | 国产精品久久久久久久久久了 | 免费黄色激情视频 | 欧美一区二区三区在线播放 | 又色又爽又黄高潮的免费视频 | 日韩网页 | 国产一区二区三区 在线 | 最新免费中文字幕 | 欧美日韩中文字幕视频 | 天天爽夜夜爽精品视频婷婷 | 天天色宗合 | 在线免费黄色 | 综合色伊人 | 91大神精品视频在线观看 | 亚洲国产午夜 | 亚洲国产精品一区二区久久hs | 最新成人在线 | 一区二区三区不卡在线 | 在线视频1卡二卡三卡 | 人人看人人草 | 日产乱码一二三区别在线 | 日韩 在线观看 | 久久这里有 | 久久免费国产精品1 | 亚洲精品在线观看av | 五月婷婷久草 | 激情婷婷综合网 | 色av资源网| 黄色在线观看www | 欧美日韩视频在线观看免费 | 国产在线自 | 免费观看久久 | 中文字幕乱在线伦视频中文字幕乱码在线 | 91高清免费在线观看 | www.色午夜.com | 一区二区三区播放 | 国产精品久久精品 | 欧美日韩aa | 人人澡超碰碰 | 亚洲精品玖玖玖av在线看 | 日韩xxxbbb | 国产啊v在线观看 | 成人国产精品入口 | 丝袜美腿一区 | 亚洲国内精品视频 | 人人射人人射 | 精品美女在线观看 | 久久久2o19精品| 狠狠操欧美 | 午夜私人影院久久久久 | www.亚洲精品视频 | 91久久久久久久 | 久久九九视频 | 国产精品手机在线观看 | 波多野结衣亚洲一区二区 | 日韩系列在线 | 91精品老司机久久一区啪 | 精品国产成人av在线免 | 精品中文字幕在线播放 | 五月激情片 | 日韩资源在线 | 久久天天躁夜夜躁狠狠85麻豆 | 狠狠干天天色 | 人人插人人澡 | 人人玩人人添人人澡超碰 | 亚洲精品影院在线观看 | av观看在线观看 | 国产xxxx做受性欧美88 | 久久国产亚洲 | 播五月综合 | 久久精品专区 | 精品国产一区二区三区久久久蜜臀 | 国产日韩精品一区二区在线观看播放 | 99热亚洲精品 | 亚洲一级特黄 | 中文字幕国内精品 | 五月婷婷天堂 | 去看片 | 97色噜噜| 国内视频在线观看 | www黄在线| 色在线中文字幕 | 91视频在线看 | 国产人成精品一区二区三 | 天天干天天拍天天操天天拍 | 一区二区视 | 国产精品免费大片视频 | 一区二区视频在线观看免费 | 在线免费看黄色 | 国产一区二区三精品久久久无广告 | 欧美成人手机版 | 国产经典 欧美精品 | 午夜精品久久久久久久99 | 日本中文字幕在线电影 | 亚洲91精品在线观看 | 精品国产一二三 | 五月天网页 | 欧美一级艳片视频免费观看 | 国产91在线观 | 丰满少妇高潮在线观看 | 91少妇精拍在线播放 | 亚洲精品女 | 日本三级在线观看中文字 | 亚洲精品视频 | 午夜电影久久久 | 麻豆 free xxxx movies hd| 一区二区三区电影在线播 | 三级黄色在线观看 | 91香蕉国产在线观看软件 | 日韩在线观看高清 | 中文字幕在线观看播放 | 草久在线观看视频 | 99亚洲天堂 | 免费日韩视| 少妇性xxx| 免费成人在线网站 | 99在线精品视频在线观看 | 成人h动漫精品一区二 | 久久免费在线观看视频 | 中文字幕一区二区在线播放 | 成人毛片在线视频 | 欧美精品亚洲精品 | 国产资源中文字幕 | 亚洲一级电影 | 五月天久久激情 | 国产拍揄自揄精品视频麻豆 | 亚洲一区日韩精品 | 国产日韩中文字幕 | 激情视频一区二区三区 | 久久精品99久久久久久 | 99精品免费 | 欧美a级在线免费观看 | 日本天天操 | 91香蕉视频 | 在线免费视频 你懂得 | 成人视屏免费看 | 中文字幕一区二区三 | 久久成人黄色 | 亚洲精品国产麻豆 | 在线色资源 | 久久精品国产亚洲精品2020 | 99精品热视频 | 欧美精品在线观看免费 | 日韩三级精品 | 免费在线观看日韩欧美 | 日韩 在线 | 国产字幕在线看 | 日日激情 | 97人人爽 | 一区二区三区免费在线观看 | 国内精品久久久久久久影视简单 | 99国产精品一区 | 五月天久久婷 | 欧美日韩一区二区在线观看 | 久久国产精品一二三区 | 亚洲精品91天天久久人人 | 久久精品资源 | 精品国产一区二区三区久久久蜜臀 | 久久黄色小说视频 | www欧美色| 国内丰满少妇猛烈精品播放 | 国产精品久久久久久久久软件 | 日韩中文在线播放 | 五月天堂网 | 一级黄色免费 | 国产精品美女久久久久久久久 | 国产精品久久视频 | 日韩黄色一区 | 免费av高清 | 四虎视频| 中文字幕在线观看网站 | 天天操夜夜操天天射 | 亚洲视频分类 | 国产最新在线观看 | 国产伦精品一区二区三区高清 | 免费a v在线| 国产不卡一区二区视频 | 日韩视频一区二区三区在线播放免费观看 | 欧美日韩国产亚洲乱码字幕 | 人人澡视频 | 久久久久久福利 | 欧美成年人在线观看 | 99c视频在线 | 欧美激情xxxx性bbbb | 一区二区国产精品 | 国模视频一区二区 | 久久超 | 久久成人免费 | www亚洲精品 | 日本精品一区二区在线观看 | 午夜色性片| 国产伦精品一区二区三区在线 |