2006年8月15日
#
0.引言
在ChinaITLAB導(dǎo)師制輔導(dǎo)中,筆者發(fā)現(xiàn)問得最多的問題莫過于"如何學(xué)習(xí)編程?JAVA該如何學(xué)習(xí)?"。類似的問題回答多了,難免會感覺厭煩,就萌生了寫下本文的想法。到時候再有人問起類似的問題,我可以告訴他(她),請你去看看《JAVA學(xué)習(xí)之路》。拜讀過臺灣蔡學(xué)鏞先生的《JAVA夜未眠》,有些文章如《JAVA學(xué)習(xí)之道》等讓我們確實(shí)有共鳴,本文題目也由此而來。
軟件開發(fā)之路是充滿荊棘與挑戰(zhàn)之路,也是充滿希望之路。JAVA學(xué)習(xí)也是如此,沒有捷徑可走。夢想像《天龍八部》中虛竹一樣被無崖子醍醐灌頂而輕松獲得一甲子功力,是很不現(xiàn)實(shí)的。每天仰天大叫"天神啊,請賜給我一本葵花寶典吧",殊不知即使你獲得了葵花寶典,除了受自宮其身之苦外,你也不一定成得了"東方不敗",倒是成"西方失敗"的幾率高一點(diǎn)。
"不走彎路,就是捷徑",佛經(jīng)說的不無道理。
1.如何學(xué)習(xí)程序設(shè)計(jì)?
JAVA是一種平臺,也是一種程序設(shè)計(jì)語言,如何學(xué)好程序設(shè)計(jì)不僅僅適用于JAVA,對C++等其他程序設(shè)計(jì)語言也一樣管用。有編程高手認(rèn)為,JAVA也好C也好沒什么分別,拿來就用。為什么他們能達(dá)到如此境界?我想是因?yàn)榫幊陶Z言之間有共通之處,領(lǐng)會了編程的精髓,自然能夠做到一通百通。如何學(xué)習(xí)程序設(shè)計(jì)理所當(dāng)然也有許多共通的地方。
1.1 培養(yǎng)興趣
興趣是能夠讓你堅(jiān)持下去的動力。如果只是把寫程序作為謀生的手段的話,你會活的很累,也太對不起自己了。多關(guān)心一些行業(yè)趣事,多想想蓋茨。不是提倡天天做白日夢,但人要是沒有了夢想,你覺得有味道嗎?可能像許多深圳本地農(nóng)民一樣,打打麻將,喝喝功夫茶,拜拜財(cái)神爺;每個月就有幾萬十幾萬甚至更多的進(jìn)帳,憑空多出個"食利階層"。你認(rèn)為,這樣有味道嗎?有空多到一些程序員論壇轉(zhuǎn)轉(zhuǎn),你會發(fā)現(xiàn),他們其實(shí)很樂觀幽默,時不時會冒出智慧的火花。
1.2 慎選程序設(shè)計(jì)語言
男怕入錯行,女怕嫁錯郎。初學(xué)者選擇程序設(shè)計(jì)語言需要謹(jǐn)慎對待。軟件開發(fā)不僅僅是掌握一門編程語言了事,它還需要其他很多方面的背景知識。軟件開發(fā)也不僅僅局限于某幾個領(lǐng)域,而是已經(jīng)滲透到了各行各業(yè)幾乎每一個角落。
如果你對硬件比較感興趣,你可以學(xué)習(xí)C語言/匯編語言,進(jìn)入硬件開發(fā)領(lǐng)域。如果你對電信的行業(yè)知識及網(wǎng)絡(luò)比較熟悉,你可以在C/C++等之上多花時間,以期進(jìn)入電信軟件開發(fā)領(lǐng)域。如果你對操作系統(tǒng)比較熟悉,你可以學(xué)習(xí)C/Linux等等,為Linux內(nèi)核開發(fā)/驅(qū)動程序開發(fā)/嵌入式開發(fā)打基礎(chǔ)。如果你想介入到應(yīng)用范圍最廣泛的應(yīng)用軟件開發(fā)(包括電子商務(wù)電子政務(wù)系統(tǒng))的話,你可以選擇J2EE或.NET,甚至LAMP組合。每個領(lǐng)域要求的背景知識不一樣。做應(yīng)用軟件需要對數(shù)據(jù)庫等很熟悉。總之,你需要根據(jù)自己的特點(diǎn)來選擇合適你的編程語言。
1.3 要腳踏實(shí)地,快餐式的學(xué)習(xí)不可取
先分享一個故事。
有一個小朋友,他很喜歡研究生物學(xué),很想知道那些蝴蝶如何從蛹?xì)だ锍鰜恚兂珊銜w。? 有一次,他走到草原上面看見一個蛹,便取了回家,然后看著,過了幾天以后,這個蛹出了一條裂痕,看見里面的蝴蝶開始掙扎,想抓破蛹?xì)わw出來。? 這個過程達(dá)數(shù)小時之久,蝴蝶在蛹里面很辛苦地拼命掙扎,怎么也沒法子走出來。這個小孩看著看著不忍心,就想不如讓我?guī)蛶退桑汶S手拿起剪刀在蛹上剪開,使蝴蝶破蛹而出。? 但蝴蝶出來以后,因?yàn)槌岚虿粔蛄Γ兊煤苡纺[,飛不起來。
這個故事給我們的啟示是:欲速則不達(dá)。
浮躁是現(xiàn)代人最普遍的心態(tài),能怪誰?也許是貧窮落后了這么多年的緣故,就像當(dāng)年的大躍進(jìn)一樣,都想大步跨入共產(chǎn)主義社會。現(xiàn)在的軟件公司、客戶、政府、學(xué)校、培訓(xùn)機(jī)構(gòu)等等到處彌漫著浮躁之氣。就拿筆者比較熟悉的深圳IT培訓(xùn)行業(yè)來說吧,居然有的打廣告宣稱"參加培訓(xùn),100%就業(yè)",居然報名的學(xué)生不少,簡直是藐視天下程序員。社會環(huán)境如是,我們不能改變,只能改變自己,鬧市中的安寧,彌足珍貴。許多初學(xué)者C++/JAVA沒開始學(xué),立馬使用VC/JBuilder,會使用VC/JBuilder開發(fā)一個Hello? World程序,就忙不迭的向世界宣告,"我會軟件開發(fā)了",簡歷上也大言不慚地寫上"精通VC/JAVA"。結(jié)果到軟件公司面試時要么被三兩下打發(fā)走了,要么被駁的體無完膚,無地自容。到處碰壁之后才知道捧起《C++編程思想》《JAVA編程思想》仔細(xì)鉆研,早知如此何必當(dāng)初呀。
"你現(xiàn)在講究簡單方便,你以后的路就長了",好象也是佛經(jīng)中的勸戒。
1.4 多實(shí)踐,快實(shí)踐
彭端淑的《為學(xué)一首示子侄》中有窮和尚與富和尚的故事。
從前,四川邊境有兩個和尚,一個貧窮,一個有錢。一天,窮和尚對富和尚說:"我打算去南海朝圣,你看怎么樣?"富和尚說:"這里離南海有幾千里遠(yuǎn),你靠什么去呢?"窮和尚說:"我只要一個水缽,一個飯碗就夠了。"富和尚為難地說:"幾年前我就打算買條船去南海,可至今沒去成,你還是別去吧!"? 一年以后,富和尚還在為租賃船只籌錢,窮和尚卻已經(jīng)從南海朝圣回來了。
這個故事可解讀為:任何事情,一旦考慮好了,就要馬上上路,不要等到準(zhǔn)備周全之后,再去干事情。假如事情準(zhǔn)備考慮周全了再上路的話,別人恐怕捷足先登了。軟件開發(fā)是一門工程學(xué)科,注重的就是實(shí)踐,"君子動口不動手"對軟件開發(fā)人員來講根本就是錯誤的,他們提倡"動手至上",但別害怕,他們大多溫文爾雅,沒有暴力傾向,雖然有時候蓬頭垢面的一副"比爾蓋茨"樣。有前輩高人認(rèn)為,學(xué)習(xí)編程的秘訣是:編程、編程、再編程,筆者深表贊同。不僅要多實(shí)踐,而且要快實(shí)踐。我們在看書的時候,不要等到你完全理解了才動手敲代碼,而是應(yīng)該在看書的同時敲代碼,程序運(yùn)行的各種情況可以讓你更快更牢固的掌握知識點(diǎn)。
1.5 多參考程序代碼
程序代碼是軟件開發(fā)最重要的成果之一,其中滲透了程序員的思想與靈魂。許多人被《仙劍奇?zhèn)b傳》中凄美的愛情故事感動,悲劇的結(jié)局更有一種缺憾美。為什么要以悲劇結(jié)尾?據(jù)說是因?yàn)閷憽断蓜ζ鎮(zhèn)b傳》的程序員失戀而安排了這樣的結(jié)局,他把自己的感覺融入到游戲中,卻讓眾多的仙劍迷扼腕嘆息。
多多參考代碼例子,對JAVA而言有參考文獻(xiàn)[4.3],有API類的源代碼(JDK安裝目錄下的src.zip文件),也可以研究一些開源的軟件或框架。
1.6 加強(qiáng)英文閱讀能力
對學(xué)習(xí)編程來說,不要求英語,? 但不能一點(diǎn)不會,。最起碼像JAVA? API文檔(參考文獻(xiàn)[4.4])這些東西還是要能看懂的,連猜帶懵都可以;旁邊再開啟一個"金山詞霸"。看多了就會越來越熟練。在學(xué)JAVA的同時學(xué)習(xí)英文,一箭雙雕多好。另外好多軟件需要到英文網(wǎng)站下載,你要能夠找到它們,這些是最基本的要求。英語好對你學(xué)習(xí)有很大的幫助。口語好的話更有機(jī)會進(jìn)入管理層,進(jìn)而可以成為剝削程序員的"周扒皮"。
1.7 萬不得已才請教別人
筆者在ChinaITLab網(wǎng)校的在線輔導(dǎo)系統(tǒng)中解決學(xué)生問題時發(fā)現(xiàn),大部分的問題學(xué)生稍做思考就可以解決。請教別人之前,你應(yīng)該先回答如下幾個問題。
你是否在google中搜索了問題的解決辦法?
你是否查看了JAVA? API文檔?
你是否查找過相關(guān)書籍?
你是否寫代碼測試過?
如果回答都是"是"的話,而且還沒有找到解決辦法,再問別人不遲。要知道獨(dú)立思考的能力對你很重要。要知道程序員的時間是很寶貴的。
1.8 多讀好書
書中自有顏如玉。比爾?蓋茨是一個飽讀群書的人。雖然沒有讀完大學(xué),但九歲的時候比爾?蓋茨就已經(jīng)讀完了所有的百科全書,所以他精通天文、歷史、地理等等各類學(xué)科,可以說比爾?蓋茨不僅是當(dāng)今世界上金錢的首富,而且也可以稱得上是知識的巨富。
筆者在給學(xué)生上課的時候經(jīng)常會給他們推薦書籍,到后來學(xué)生實(shí)在忍無可忍開始抱怨,"天吶,這么多書到什么時候才能看完了","學(xué)軟件開發(fā),感覺上了賊船"。這時候,我的回答一般是,"別著急,什么時候帶你們?nèi)タ纯次业臅浚浆F(xiàn)在每月花在技術(shù)書籍上的錢400元,這在軟件開發(fā)人員之中還只能夠算是中等的",學(xué)生當(dāng)場暈倒。(注:這一部分學(xué)生是剛學(xué)軟件開發(fā)的)
對于在JAVA開發(fā)領(lǐng)域的好書在筆者另外一篇文章中會專門點(diǎn)評。該文章可作為本文的姊妹篇。
1.9 使用合適的工具
工欲善其事必先利其器。軟件開發(fā)包含各種各樣的活動,需求收集分析、建立用例模型、建立分析設(shè)計(jì)模型、編程實(shí)現(xiàn)、調(diào)試程序、自動化測試、持續(xù)集成等等,沒有工具幫忙可以說是寸步難行。工具可以提高開發(fā)效率,使軟件的質(zhì)量更高BUG更少。組合稱手的武器。到飛花摘葉皆可傷人的境界就很高了,無招勝有招,手中無劍心中有劍這樣的境界幾乎不可企及。在筆者另外一篇文章中會專門闡述如何選擇合適的工具(該文章也可作為本文的姊妹篇)。
2.軟件開發(fā)學(xué)習(xí)路線
兩千多年的儒家思想孔孟之道,中庸的思想透入骨髓,既不冒進(jìn)也不保守并非中庸之道,而是找尋學(xué)習(xí)軟件開發(fā)的正確路線與規(guī)律。
從軟件開發(fā)人員的生涯規(guī)劃來講,我們可以大致分為三個階段,軟件工程師→軟件設(shè)計(jì)師→架構(gòu)設(shè)計(jì)師或項(xiàng)目管理師。不想當(dāng)元帥的士兵不是好士兵,不想當(dāng)架構(gòu)設(shè)計(jì)師或項(xiàng)目管理師的程序員也不是好的程序員。我們應(yīng)該努力往上走。讓我們先整理一下開發(fā)應(yīng)用軟件需要學(xué)習(xí)的主要技術(shù)。
A.基礎(chǔ)理論知識,如操作系統(tǒng)、編譯原理、數(shù)據(jù)結(jié)構(gòu)與算法、計(jì)算機(jī)原理等,它們并非不重要。如不想成為計(jì)算機(jī)科學(xué)家的話,可以采取"用到的時候再來學(xué)"的原則。
B.一門編程語言,現(xiàn)在基本上都是面向?qū)ο蟮恼Z言,JAVA/C++/C#等等。如果做WEB開發(fā)的話還要學(xué)習(xí)HTML/JavaScript等等。
C.一種方法學(xué)或者說思想,現(xiàn)在基本都是面向?qū)ο笏枷耄∣OA/OOD/設(shè)計(jì)模式)。由此而衍生的基于組件開發(fā)CBD/面向方面編程AOP等等。
D.一種關(guān)系型數(shù)據(jù)庫,ORACLE/SqlServer/DB2/MySQL等等
E.一種提高生產(chǎn)率的IDE集成開發(fā)環(huán)境JBuilder/Eclipse/VS.NET等。
F.一種UML建模工具,用ROSE/VISIO/鋼筆進(jìn)行建模。
G.一種軟件過程,RUP/XP/CMM等等,通過軟件過程來組織軟件開發(fā)的眾多活動,使開發(fā)流程專業(yè)化規(guī)范化。當(dāng)然還有其他的一些軟件工程知識。
H.項(xiàng)目管理、體系結(jié)構(gòu)、框架知識。
正確的路線應(yīng)該是:B→C→E→F→G→H。
還需要補(bǔ)充幾點(diǎn):
1).對于A與C要補(bǔ)充的是,我們應(yīng)該在實(shí)踐中逐步領(lǐng)悟編程理論與編程思想。新技術(shù)雖然不斷涌現(xiàn),更新速度令人眼花燎亂霧里看花;但萬變不離其宗,編程理論與編程思想的變化卻很慢。掌握了編程理論與編程思想你就會有撥云見日之感。面向?qū)ο蟮乃枷朐谀壳皝碇v是相當(dāng)關(guān)鍵的,是強(qiáng)勢技術(shù)之一,在上面需要多投入時間,給你的回報也會讓你驚喜。
2).對于數(shù)據(jù)庫來說是獨(dú)立學(xué)習(xí)的,這個時機(jī)就由你來決定吧。
3).編程語言作為學(xué)習(xí)軟件開發(fā)的主線,而其余的作為輔線。
4).軟件工程師著重于B、C、E、? D;軟件設(shè)計(jì)師著重于B、C、E、? D、F;架構(gòu)設(shè)計(jì)師著重于C、F、H。
3.如何學(xué)習(xí)JAVA?
3.1? JAVA學(xué)習(xí)路線
3.1.1? 基礎(chǔ)語法及JAVA原理
基礎(chǔ)語法和JAVA原理是地基,地基不牢靠,猶如沙地上建摩天大廈,是相當(dāng)危險的。學(xué)習(xí)JAVA也是如此,必須要有扎實(shí)的基礎(chǔ),你才能在J2EE、J2ME領(lǐng)域游刃有余。參加SCJP(SUN公司認(rèn)證的JAVA程序員)考試不失為一個好方法,原因之一是為了對得起你交的1200大洋考試費(fèi),你會更努力學(xué)習(xí),原因之二是SCJP考試能夠讓你把基礎(chǔ)打得很牢靠,它要求你跟JDK一樣熟悉JAVA基礎(chǔ)知識;但是你千萬不要認(rèn)為考過了SCJP就有多了不起,就能夠獲得軟件公司的青睞,就能夠獲取高薪,這樣的想法也是很危險的。獲得"真正"的SCJP只能證明你的基礎(chǔ)還過得去,但離實(shí)際開發(fā)還有很長的一段路要走。
3.1.2? OO思想的領(lǐng)悟
掌握了基礎(chǔ)語法和JAVA程序運(yùn)行原理后,我們就可以用JAVA語言實(shí)現(xiàn)面向?qū)ο蟮乃枷肓恕C嫦驅(qū)ο螅且环N方法學(xué);是獨(dú)立于語言之外的編程思想;是CBD基于組件開發(fā)的基礎(chǔ);屬于強(qiáng)勢技術(shù)之一。當(dāng)以后因工作需要轉(zhuǎn)到別的面向?qū)ο笳Z言的時候,你會感到特別的熟悉親切,學(xué)起來像喝涼水這么簡單。
使用面向?qū)ο蟮乃枷脒M(jìn)行開發(fā)的基本過程是:
●調(diào)查收集需求。
●建立用例模型。
●從用例模型中識別分析類及類與類之間的靜態(tài)動態(tài)關(guān)系,從而建立分析模型。
●細(xì)化分析模型到設(shè)計(jì)模型。
●用具體的技術(shù)去實(shí)現(xiàn)。
●測試、部署、總結(jié)。
3.1.3? 基本API的學(xué)習(xí)
進(jìn)行軟件開發(fā)的時候,并不是什么功能都需要我們?nèi)?shí)現(xiàn),也就是經(jīng)典名言所說的"不需要重新發(fā)明輪子"。我們可以利用現(xiàn)成的類、組件、框架來搭建我們的應(yīng)用,如SUN公司編寫好了眾多類實(shí)現(xiàn)一些底層功能,以及我們下載過來的JAR文件中包含的類,我們可以調(diào)用類中的方法來完成某些功能或繼承它。那么這些類中究竟提供了哪些方法給我們使用?方法的參數(shù)個數(shù)及類型是?類的構(gòu)造器需不需要參數(shù)?總不可能SUN公司的工程師打國際長途甚至飄洋過海來告訴你他編寫的類該如何使用吧。他們只能提供文檔給我們查看,JAVA? DOC文檔(參考文獻(xiàn)4.4)就是這樣的文檔,它可以說是程序員與程序員交流的文檔。
基本API指的是實(shí)現(xiàn)了一些底層功能的類,通用性較強(qiáng)的API,如字符串處理/輸入輸出等等。我們又把它成為類庫。熟悉API的方法一是多查JAVA? DOC文檔(參考文獻(xiàn)4.4),二是使用JBuilder/Eclipse等IDE的代碼提示功能。
3.1.4? 特定API的學(xué)習(xí)
JAVA介入的領(lǐng)域很廣泛,不同的領(lǐng)域有不同的API,沒有人熟悉所有的API,對一般人而言只是熟悉工作中要用到的API。如果你做界面開發(fā),那么你需要學(xué)習(xí)Swing/AWT/SWT等API;如果你進(jìn)行網(wǎng)絡(luò)游戲開發(fā),你需要深入了解網(wǎng)絡(luò)API/多媒體API/2D3D等;如果你做WEB開發(fā),就需要熟悉Servlet等API啦。總之,需要根據(jù)工作的需要或你的興趣發(fā)展方向去選擇學(xué)習(xí)特定的API。
3.1.5? 開發(fā)工具的用法
在學(xué)習(xí)基礎(chǔ)語法與基本的面向?qū)ο蟾拍顣r,從鍛煉語言熟練程度的角度考慮,我們推薦使用的工具是Editplus/JCreator+JDK,這時候不要急于上手JBuilder/Eclipse等集成開發(fā)環(huán)境,以免過于關(guān)注IDE的強(qiáng)大功能而分散對JAVA技術(shù)本身的注意力。過了這一階段你就可以開始熟悉IDE了。
程序員日常工作包括很多活動,編輯、編譯及構(gòu)建、調(diào)試、單元測試、版本控制、維持模型與代碼同步、文檔的更新等等,幾乎每一項(xiàng)活動都有專門的工具,如果獨(dú)立使用這些工具的話,你將會很痛苦,你需要在堆滿工具的任務(wù)欄上不斷的切換,效率很低下,也很容易出錯。在JBuilder、Eclipse等IDE中已經(jīng)自動集成編輯器、編譯器、調(diào)試器、單元測試工具JUnit、自動構(gòu)建工具ANT、版本控制工具CVS、DOC文檔生成與更新等等,甚至可以把UML建模工具也集成進(jìn)去,又提供了豐富的向?qū)椭煽蚣艽a,讓我們的開發(fā)變得更輕松。應(yīng)該說IDE發(fā)展的趨勢就是集成軟件開發(fā)中要用到的幾乎所有工具。
從開發(fā)效率的角度考慮,使用IDE是必經(jīng)之路,也是從一個學(xué)生到一個職業(yè)程序員轉(zhuǎn)變的里程碑。
JAVA開發(fā)使用的IDE主要有Eclipse、JBuilder、JDeveloper、NetBeans等幾種;而Eclipse、JBuilder占有的市場份額是最大的。JBuilder在近幾年來一直是JAVA集成開發(fā)環(huán)境中的霸主,它是由備受程序員尊敬的Borland公司開發(fā),在硝煙彌漫的JAVA? IDE大戰(zhàn)中,以其快速的版本更新?lián)魯BM的Visual? Age? for? JAVA等而成就一番偉業(yè)。IBM在Visual? Age? for? JAVA上已經(jīng)無利可圖之下,干脆將之貢獻(xiàn)給開源社區(qū),成為Eclipse的前身,真所謂"柳暗花明又一村"。浴火重生的Eclipse以其開放式的插件擴(kuò)展機(jī)制、免費(fèi)開源獲得廣大程序員(包括幾乎所有的骨灰級程序員)的青睞,極具發(fā)展?jié)摿Α?
3.1.6? 學(xué)習(xí)軟件工程
對小型項(xiàng)目而言,你可能認(rèn)為軟件工程沒太大的必要。隨著項(xiàng)目的復(fù)雜性越來越高,軟件工程的必要性才會體現(xiàn)出來。參見"軟件開發(fā)學(xué)習(xí)路線"小節(jié)。
3.2學(xué)習(xí)要點(diǎn)
確立的學(xué)習(xí)路線之后,我們還需要總結(jié)一下JAVA的學(xué)習(xí)要點(diǎn),這些要點(diǎn)在前文多多少少提到過,只是筆者覺得這些地方特別要注意才對它們進(jìn)行匯總,不要嫌我婆婆媽媽啊。
3.2.1勤查API文檔
當(dāng)程序員編寫好某些類,覺得很有成就感,想把它貢獻(xiàn)給各位苦難的同行。這時候你要使用"javadoc"工具(包含在JDK中)生成標(biāo)準(zhǔn)的JAVA? DOC文檔,供同行使用。J2SE/J2EE/J2ME的DOC文檔是程序員與程序員交流的工具,幾乎人手一份,除了菜鳥之外。J2SE? DOC文檔官方下載地址:
http://java.sun.com/j2se/1.5.0/download.jsp,你可以到google搜索CHM版本下載。也可以在線查看:
http://java.sun.com/j2se/1.5.0/docs/api/index.html。
對待DOC文檔要像毛主席語錄,早上起床念一遍,吃飯睡覺前念一遍。
當(dāng)需要某項(xiàng)功能的時候,你應(yīng)該先查相應(yīng)的DOC文檔看看有沒有現(xiàn)成的實(shí)現(xiàn),有的話就不必勞神費(fèi)心了直接用就可以了,找不到的時候才考慮自己實(shí)現(xiàn)。使用步驟一般如下:
●找特定的包,包一般根據(jù)功能組織。
●找需要使用類,類命名規(guī)范的話我們由類的名字可猜出一二。
●選擇構(gòu)造器,大多數(shù)使用類的方式是創(chuàng)建對象。
●選擇你需要的方法。
3.2.2? 查書/google->寫代碼測試->查看源代碼->請教別人
當(dāng)我們遇到問題的時候該如何解決?
這時候不要急著問別人,太簡單的問題,沒經(jīng)過思考的問題,別人會因此而瞧不起你。可以先找找書,到google中搜一下看看,絕大部分問題基本就解決了。而像"某些類/方法如何使用的問題",DOC文檔就是答案。對某些知識點(diǎn)有疑惑是,寫代碼測試一下,會給你留下深刻的印象。而有的問題,你可能需要直接看API的源代碼驗(yàn)證你的想法。萬不得已才去請教別人。
3.2.3學(xué)習(xí)開源軟件的設(shè)計(jì)思想
JAVA領(lǐng)域有許多源代碼開放的工具、組件、框架,JUnit、ANT、Tomcat、Struts、Spring、Jive論壇、PetStore寵物店等等多如牛毛。這些可是前輩給我們留下的瑰寶呀。入寶山而空手歸,你心甘嗎?對這些工具、框架進(jìn)行分析,領(lǐng)會其中的設(shè)計(jì)思想,有朝一日說不定你也能寫一個XXX框架什么的,風(fēng)光一把。分析開源軟件其實(shí)是你提高技術(shù)、提高實(shí)戰(zhàn)能力的便捷方法。
3.2.4? 規(guī)范的重要性
沒有規(guī)矩,不成方圓。這里的規(guī)范有兩層含義。第一層含義是技術(shù)規(guī)范,多到
http://www.jcp.org下載JSRXXX規(guī)范,多讀規(guī)范,這是最權(quán)威準(zhǔn)確最新的教材。第二層含義是編程規(guī)范,如果你使用了大量的獨(dú)特算法,富有個性的變量及方法的命名方式;同時,沒給程序作注釋,以顯示你的編程功底是多么的深厚。這樣的代碼別人看起來像天書,要理解談何容易,更不用說維護(hù)了,必然會被無情地掃入垃圾堆。JAVA編碼規(guī)范到此查看或下載
http://java.sun.com/docs/codeconv/,中文的也有,啊,還要問我在哪,請參考3.2.2節(jié)。
3.2.5? 不局限于JAVA
很不幸,很幸運(yùn),要學(xué)習(xí)的東西還有很多。不幸的是因?yàn)橐獙W(xué)的東西太多且多變,沒時間陪老婆家人或女朋友,導(dǎo)致身心疲憊,嚴(yán)重者甚至導(dǎo)致抑郁癥。幸運(yùn)的是別人要搶你飯碗絕非易事,他們或她們需要付出很多才能達(dá)成心愿。
JAVA不要孤立地去學(xué)習(xí),需要綜合學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)、OOP、軟件工程、UML、網(wǎng)絡(luò)編程、數(shù)據(jù)庫技術(shù)等知識,用橫向縱向的比較聯(lián)想的方式去學(xué)習(xí)會更有效。如學(xué)習(xí)JAVA集合的時候找數(shù)據(jù)結(jié)構(gòu)的書看看;學(xué)JDBC的時候復(fù)習(xí)數(shù)據(jù)庫技術(shù);采取的依然是"需要的時候再學(xué)"的原則。
4.結(jié)束語
需要強(qiáng)調(diào)的是,學(xué)習(xí)軟件開發(fā)確實(shí)有一定的難度,也很辛苦,需要付出很多努力,但千萬不要半途而廢。本文如果能對一直徘徊在JAVA神殿之外的朋友有所幫助的話,筆者也欣慰了。哈哈,怎么聽起來老氣橫秋呀?沒辦法,在電腦的長期輻射之下,都快變成小老頭了。最后奉勸各位程序員尤其是MM程序員,完成工作后趕快遠(yuǎn)離電腦,據(jù)《胡播亂報》報道,電腦輻射會在白皙的皮膚上面點(diǎn)綴一些小黑點(diǎn),看起來鮮艷無比……
5.參考文獻(xiàn)
5.1《JAVA夜未眠》
5.2?
http://www.chinaitlab.com/www/news/article_show.asp?id=33934 5.3?
http://javaalmanac.com/egs/ 5.4?
http://java.sun.com/j2se/1.5.0/docs/api/index.html
摘要: 繼續(xù)檢查
rental
類中函數(shù)
getCharge()
的語句
switch (getMovie().getPriceCode())
,它提示我們應(yīng)該將計(jì)算
charge
的職責(zé)交給
movie
類來完成。這是租借天數(shù)作為參數(shù)傳給
movie
類的相關(guān)函數(shù)進(jìn)行計(jì)算。我們在
...
閱讀全文

?
編寫的4個測試用例全部通過了,但是如果想對迭代器中每一個元素的計(jì)算結(jié)果進(jìn)行驗(yàn)證現(xiàn)有的函數(shù)就無法完成要求了。
下面運(yùn)用重構(gòu)中的抽取函數(shù)分解Customer類中超長的方法statement(),這個函數(shù)完成的功能有些無所不包了。既計(jì)算各個租借的費(fèi)用和常客積點(diǎn),又要計(jì)算客戶需要交納的費(fèi)用總和,最后還要輸出報表的表頭和表尾。另外,現(xiàn)有的快速設(shè)計(jì)對變化的應(yīng)對不足,客戶的電影分類可能會出現(xiàn)變化,如果去掉兒童片加上科幻片、故事片怎么辦,客戶的收費(fèi)規(guī)則發(fā)生變化了怎么辦。根據(jù)前面所說,他的味道不是一般的臭。
第一步:抽取函數(shù)
我們先把statement計(jì)算各個租借的費(fèi)用的職責(zé)分離到一個單獨(dú)的函數(shù)中。
private double amountFor(Rental rental){
????????????? double thisAmount=0.0;
????????????? switch (rental.getMovie().getPriceCode()) {
????????????? case Movie.REGULAR:
???????????????????? thisAmount+=2;
???????????????????? if(rental.getDayRented()>2)
??????????????????????????? thisAmount+=(rental.getDayRented()-2)*1.5;
???????????????????? break;
????????????? case Movie.CHILDRENS:
???????????????????? thisAmount+=1.5;
???????????????????? if(rental.getDayRented()>3)
??????????????????????????? thisAmount+=(rental.getDayRented()-3)*1.5;
???????????????????? break;
????????????? case Movie.NEW_RELEASE:
???????????????????? thisAmount+=(rental.getDayRented())*3;
???????????????????? break;
????????????? }
????????????? return thisAmount;
?????????????
?????? }
把原來的計(jì)算各個租借費(fèi)用的部分注釋起來,把private double thisAmount=0.0; 一句改為double thisAmount=amountFor(each);再次運(yùn)行測試用例,和第一次的結(jié)果對比

我們通過人工比對輸出結(jié)果,輸出結(jié)果相同。我們稍后會通過測試用例由套件自動比對。
如果這一步我們的測試fail掉了,由于我們只走了很小的一步,因此我們可以輕易的退回去。記住,我們始終保持測試-〉編碼(重構(gòu))-〉測試的步驟,小步快走的穩(wěn)定節(jié)奏。
測試全部通過后,我們可以果斷的刪除剛才留在statement函數(shù)中我們注釋過的臨時代碼。
分析我們剛剛抽取出來的amountFor函數(shù),它只使用了rental類中的成員變量,放在Customer
類中并不合適,因此我們繼續(xù)重構(gòu),把amountfor函數(shù)移動到Rental類中。
這里我們使用了集成開發(fā)環(huán)境中的功能:

這里我們看到eclipse自動偵測出目標(biāo)的類為rental,我們把新的函數(shù)改名為getcharge,并勾選在原類型中創(chuàng)建委托,可以點(diǎn)擊預(yù)覽按鈕察看重構(gòu)結(jié)果,最后的結(jié)果如下:
Rental 類中:
double getCharge(){
????????????? double thisAmount=0.0;
????????????? switch (getMovie().getPriceCode()) {
????????????? case Movie.REGULAR:
???????????????????? thisAmount+=2;
???????????????????? if(getDayRented()>2)
??????????????????????????? thisAmount+=(getDayRented()-2)*1.5;
???????????????????? break;
????????????? case Movie.CHILDRENS:
???????????????????? thisAmount+=1.5;
???????????????????? if(getDayRented()>3)
??????????????????????????? thisAmount+=(getDayRented()-3)*1.5;
???????????????????? break;
????????????? case Movie.NEW_RELEASE:
???????????????????? thisAmount+=(getDayRented())*3;
???????????????????? break;
????????????? }
????????????? return thisAmount;
?????????????
?????? }
可以看到比amountfor函數(shù)更加簡化。
Customer類中:
private double amountFor(Rental rental){
????????????? return rental.getCharge();
?????? }
保留了一個rental示例的委托調(diào)用。
再次運(yùn)行測試用例,通過。
我們繼續(xù)前進(jìn)!
我們回到Customer類中,察看Statement函數(shù)中的thisAmount變量,我們發(fā)現(xiàn)他的結(jié)果并沒有發(fā)生變化,我們可以通過重構(gòu)把這個無用的臨時變量除去。直接把thisamount=amountFor(each);的語句右側(cè)考到totalAmount+=thisAmount;語句右側(cè),編譯器會提示thisamount變量并未使用,直接雙擊quick fix 除去這個變量。再次運(yùn)行測試用例,通過。
下一步我們把計(jì)算常客積點(diǎn)的職責(zé)也從statement函數(shù)中分離出來:
我們添加函數(shù)
private int getFrequentCount(Rental rental){
?????????????
????????????? if (rental.getMovie().getPriceCode()==Movie.NEW_RELEASE&&rental.getDayRented()>1)
???????????????????? return 2;
????????????? else return 1;
?????? }
再把計(jì)算常客積點(diǎn)的部分改寫如下:
???????????????????? frequentCount+=getFrequentCount(each);
測試后我們發(fā)現(xiàn)常客積點(diǎn)的計(jì)算和類Rental的關(guān)系更密切,我們再次移動getFrequentCount到rental類中。測試通過后,計(jì)算常客積點(diǎn)的部分改寫如下:
???????????????????? frequentCount+= each.getFrequentCount();
接下來,我們把statement最后的兩個臨時變量frequentCount、totalAmount也通過替換為函數(shù)查詢消除掉。
添加函數(shù)
private double getTotalCharge(){
????????????? double result=0.0;
????????????? Enumeration rental=rentals.elements();
????????????? while (rental.hasMoreElements()) {
???????????????????? Rental rent = (Rental) rental.nextElement();
???????????????????? result+=rent.getCharge();
????????????? }
????????????? return result;
?????? }
把臨時變量totalAmount替換為getTotalCharge()
添加函數(shù)
private int getTotalFrequentCount(){
????????????? int result=0;
????????????? Enumeration rental=rentals.elements();
????????????? while (rental.hasMoreElements()) {
???????????????????? Rental each = (Rental) rental.nextElement();
???????????????????? result+=each.getFrequentCount();
????????????? }
????????????? return result;
?????? }
把臨時變量frequentCount替換為getTotalFrequentCount
相應(yīng)的,我們在測試中添加以下四條測試用例:
public void testGetCharge(){
????????????? Enumeration rentals=tom.getRentals().elements();
????????????? while (rentals.hasMoreElements()) {
???????????????????? Rental each = (Rental) rentals.nextElement();
???????????????????? switch(each.getMovie().getPriceCode()){
???????????????????? case Movie.CHILDRENS:
??????????????????????????? assertEquals(1.5,each.getCharge(),.1);
???????????????????????????
??????????????????????????? break;
???????????????????? case Movie.NEW_RELEASE:
??????????????????????????? assertEquals(15,each.getCharge(),.1);
??????????????????????????? break;
???????????????????? case Movie.REGULAR:
??????????????????????????? assertEquals(6.5,each.getCharge(),.1);
??????????????????????????? break;
???????????????????? }
?????? }
?????? }
????????????? public void testgetFrequentCount(){
???????????????????? Enumeration rentals=tom.getRentals().elements();
???????????????????? while (rentals.hasMoreElements()) {
??????????????????????????? Rental each = (Rental) rentals.nextElement();
??????????????????????????? switch(each.getMovie().getPriceCode()){
??????????????????????????? case Movie.CHILDRENS:
?????????????????????????????????? assertEquals(1,each.getFrequentCount(),.1);
??????????????????????????????????
?????????????????????????????????? break;
??????????????????????????? case Movie.NEW_RELEASE:
?????????????????????????????????? assertEquals(2,each.getFrequentCount(),.1);
?????????????????????????????????? break;
??????????????????????????? case Movie.REGULAR:
?????????????????????????????????? assertEquals(1,each.getFrequentCount(),.1);
?????????????????????????????????? break;
??????????????????????????? }
????????????? }
?????? }
????????????? public void testGetTotalCharge(){
???????????????????? assertEquals(23,tom.getTotalCharge(),.1);
????????????? }
????????????? public void testGetTotalFrequentCount(){
???????????????????? assertEquals(4,tom.getTotalFrequentCount(),.1);
????????????? }
現(xiàn)在我們就可以對迭代器中的每個元素的結(jié)果進(jìn)行驗(yàn)證了。
測試驅(qū)動示例,應(yīng)用重構(gòu)優(yōu)化設(shè)計(jì)
下面通過一個簡單的測試驅(qū)動示例,并經(jīng)過重構(gòu)完成設(shè)計(jì)的更改。詳細(xì)的例子見重構(gòu)。
影片出租店的程序,計(jì)算每一位顧客的消費(fèi)金額并打印報表。操作者告訴程序:顧客租用了哪些影片,租期多長,程序根據(jù)租賃時間和影片類型(普通片,兒童片和新片)。除了計(jì)算費(fèi)用,還要為常客計(jì)算點(diǎn)數(shù),點(diǎn)數(shù)的計(jì)算會由于租片種類是否為新片而有所不同。
根據(jù)上述描述,我們畫出簡單的類圖

其中影片和租借都是簡單的純數(shù)據(jù)類。
package chapter01;
public class Movie {
?????? public static final int CHILDRENS=2;//
影片類型
?????? public static final int REGULAR=1;
?????? public static final int NEW_RELEASE=0;
??????
?????? private String title;//
名稱
?????? private int priceCode;//
價格代碼
?????? public Movie(String title, int priceCode) {
????????????? this.title = title;
????????????? this.priceCode = priceCode;
?????? }
?????? public int getPriceCode() {
????????????? return priceCode;
?????? }
?????? public void setPriceCode(int priceCode) {
????????????? this.priceCode = priceCode;
?????? }
?????? public String getTitle() {
????????????? return title;
?????? }
??????
?????? }
?
public class Rental {
?????? private Movie movie;
?????? private int dayRented;
?????? public Rental(Movie movie, int dayRented) {
?????????????
????????????? this.movie = movie;
????????????? this.dayRented = dayRented;
?????? }
?????? public int getDayRented() {
????????????? return dayRented;
?????? }
?????? public Movie getMovie() {
????????????? return movie;
?????? }
??????
}
?
public class Customer {
?????? private String name;
?????? private Vector rentals=new Vector();
?????? public Customer(String name) {
?????????????
????????????? this.name = name;
?????? }
?????? public String getName() {
????????????? return name;
?????? }
?????? public Vector getRentals() {
????????????? return rentals;
?????? }
?????? @SuppressWarnings("unchecked")
?????? public void addRental(Rental rental){
????????????? rentals.add(rental);
?????? }
?????? public String statement(){//
計(jì)算費(fèi)用
????????????? double totalAmount=0;//
總和
????????????? int frequentCount=0;//
常客積點(diǎn)
????????????? Enumeration rental=rentals.elements();
????????????? String result="rental record for "+getName()+"\n";
????????????? while (rental.hasMoreElements()) {
???????????????????? Rental each = (Rental) rental.nextElement();//
取得一批租借記錄
???????????????????? double thisAmount=0;
???????????????????? //
根據(jù)類型,計(jì)算價格
???????????????????? switch (each.getMovie().getPriceCode()) {
???????????????????? case Movie.REGULAR:
??????????????????????????? thisAmount+=2;
??????????????????????????? if(each.getDayRented()>2)
?????????????????????????????????? thisAmount+=(each.getDayRented()-2)*1.5;
??????????????????????????? break;
???????????????????? case Movie.CHILDRENS:
??????????????????????????? thisAmount+=1.5;
??????????????????????????? if(each.getDayRented()>3)
?????????????????????????????????? thisAmount+=(each.getDayRented()-3)*1.5;
??????????????????????????? break;
???????????????????? case Movie.NEW_RELEASE:
??????????????????????????? thisAmount+=(each.getDayRented())*3;
??????????????????????????? break;
???????????????????? }
???????????????????? //
添加常客積點(diǎn)
???????????????????? frequentCount++;
???????????????????? if (each.getMovie().getPriceCode()==Movie.NEW_RELEASE&&each.getDayRented()>1)
??????????????????????????? frequentCount++;
???????????????????? //
顯示此筆數(shù)據(jù)
???????????????????? result+=
??????????????????????????? "\t"+each.getMovie().getTitle()+
??????????????????????????? "\t"+String.valueOf(frequentCount)+"\n";
????????????????????
???????????????????? totalAmount+=thisAmount;
????????????? }
????????????? //
打印結(jié)尾
????????????? result+="amount owned "+String.valueOf(totalAmount)+"\n";
????????????? result+="you earned "+String.valueOf(frequentCount)+"frequentCount\n";
?????????????
????????????? return result;
?????? }
}
下面是對應(yīng)的測試用例
public class testCustomerStatement extends TestCase {
?????? Movie childrenMovie=new Movie("HARRY POTTY",Movie.CHILDRENS);
?????? Movie regularMovie=new Movie("Titanic",Movie.REGULAR);
?????? Movie newMovie=new Movie("Ice Age 2",Movie.NEW_RELEASE);
??????
?????? Rental childRental=new Rental(childrenMovie,3);
?????? Rental newRental=new Rental(newMovie,5);
?????? Rental regRental=new Rental(regularMovie,5);
??????
?????? Customer tom=new Customer("Tom");
??????
?????? protected void setUp() throws Exception {
????????????? super.setUp();
????????????? tom.addRental(childRental);
????????????? tom.addRental(newRental);
????????????? tom.addRental(regRental);
?????? }
??????
?????? public void testCaustomerName(){
?????? assertEquals("Tom",tom.getName());
??????
??????
?????? }
?????? public void testIteratorSize(){
?????????????
?????? assertEquals(3,tom.getRentals().size());??????
?????? }
?????? public void testIteratorName(){
????????????? Enumeration rentals=tom.getRentals().elements();
????????????? while (rentals.hasMoreElements()) {
???????????????????? Rental each = (Rental) rentals.nextElement();
???????????????????? switch(each.getMovie().getPriceCode()){
???????????????????? case Movie.CHILDRENS:
??????????????????????????? System.out.println("childrens");
??????????????????????????? assertEquals("HARRY POTTY",each.getMovie().getTitle());
??????????????????????????? assertEquals(3,each.getDayRented());
???????????????????????????
??????????????????????????? break;
???????????????????? case Movie.NEW_RELEASE:
??????????????????????????? System.out.println("new");
??????????????????????????? assertEquals("Ice Age 2",each.getMovie().getTitle());
??????????????????????????? assertEquals(5,each.getDayRented());
??????????????????????????? break;
???????????????????? case Movie.REGULAR:
??????????????????????????? System.out.println("regular");
??????????????????????????? assertEquals("Titanic",each.getMovie().getTitle());
??????????????????????????? assertEquals(5,each.getDayRented());
??????????????????????????? break;
???????????????????? }
???????????????????????????
????????????? }
?????? }
?????? public void testOutput(){
????????????? System.out.println(tom.statement());
?????? }
}
面向?qū)ο筌浖_發(fā)的敏捷過程
軟件開發(fā)的復(fù)雜性
:
計(jì)算機(jī)硬件界的摩爾定律(每隔
18
個月計(jì)算機(jī)硬件的運(yùn)算速度提高一倍,價格下降一半)
適用于硬件的發(fā)展規(guī)律已經(jīng)超過三十年了。人們想當(dāng)然的認(rèn)為計(jì)算機(jī)軟件的發(fā)展速度和硬件的發(fā)展速度相當(dāng),但是不幸的是:每次重大的硬件升級之后,隨著更大功能更豐富的軟件的出現(xiàn),硬件的潛能再一次被無情的榨取殆盡。許多開發(fā)的軟件系統(tǒng)不斷的遭受進(jìn)度延期,人員資金和時間等預(yù)算無休止的增加,軟件質(zhì)量的不斷反復(fù),開發(fā)出來的系統(tǒng)對客戶的新需求響應(yīng)緩慢,更改困難的噩夢。
這樣的現(xiàn)實(shí)是由軟件的固有復(fù)雜性造成的,軟件不同于硬件的生產(chǎn)過程,是由人的智力勞動完成人的需求到機(jī)器程序的翻譯轉(zhuǎn)換過程。需求可能不清晰,對需求可能出現(xiàn)個人理解上的差異,選擇實(shí)現(xiàn)方法的差異,需求的不斷變化,具體實(shí)現(xiàn)語言平臺的差異,軟件生產(chǎn)中采用的過程,具體實(shí)現(xiàn)人員的變動等等都會對最終的產(chǎn)品產(chǎn)生影響。想象一下,如果一種變化的因素只有兩種可能,那么可以使用簡單的
0
,
1
表示,只有
10
個變化因素的組合就已經(jīng)達(dá)到了
2--10=1024
種可能性,而實(shí)際開發(fā)中變化的因素輕易就超過
10
個以上,每個變化的可能是還不止兩個,因此軟件的復(fù)雜性很快就會超出人的理解程度。有一句經(jīng)典的軟件開發(fā)名言:世界上唯一不變的是變化本身。不斷出現(xiàn)的變化,會使初始的設(shè)計(jì)和最終的需求之間的距離越來越遠(yuǎn)。
軟件的臭味:
軟件開發(fā),使用,維護(hù)中出現(xiàn)了以下的“臭味”:
僵化性:
rigidity
很難對系統(tǒng)進(jìn)行改動,因?yàn)槊總€改動都會迫使許多對系統(tǒng)其他部分的其他改動。即使是簡單的改動,也會迫使導(dǎo)致右依賴關(guān)系的模塊的連鎖改動。
脆弱性:
fragility
對系統(tǒng)的改動會導(dǎo)致系統(tǒng)中和改動的地方在概念上無關(guān)的許多地方出現(xiàn)問題。出現(xiàn)新問題的地方和改動的地方?jīng)]有概念上的關(guān)聯(lián),難以排錯,排錯的過程中又會引入更多的“臭蟲”。
牢固性
immobility
很難解開系統(tǒng)的糾結(jié),使它成為其他系統(tǒng)中重用的組件。系統(tǒng)中包含了對其他系統(tǒng)中有用的功能,當(dāng)其他人想復(fù)用這個功能到新的系統(tǒng)時,剝離出獨(dú)立的組件的難度遠(yuǎn)遠(yuǎn)大于重新實(shí)現(xiàn)的難度,在時間和進(jìn)度的壓力下,大多數(shù)人只有選擇拷貝涂鴉的方式來實(shí)現(xiàn)新系統(tǒng)的功能。
粘滯性:
viscosity
做正確的事情比錯誤的事情要困難。程序完成正常的功能總是傾向于得到不正確的結(jié)果。
不必要的復(fù)雜性:
needless complexity
設(shè)計(jì)中包含有不具有任何直接好處的基礎(chǔ)結(jié)構(gòu)。為了預(yù)防后期維護(hù)更改需求的對源碼的修改,在設(shè)計(jì)之初放置了那些處理潛在變化的代碼來保持軟件的靈活性,這樣的結(jié)果是軟件中包含了很多復(fù)雜的結(jié)構(gòu),理解起來更加困難。
不必要的重復(fù):
needless repetition
設(shè)計(jì)中包含有重復(fù)的結(jié)構(gòu),而該重復(fù)的結(jié)構(gòu)可以使用單一的抽象進(jìn)行統(tǒng)一。對鼠標(biāo)右鍵(剪切,復(fù)制,粘貼)的濫用,使得完成同一或類似的代碼片斷出現(xiàn)在系統(tǒng)各處。如果原始的代碼段完成的功能需要變化,或者存在錯誤,排錯和增加新的功能變得非常困難。
晦澀性:
opacity
很難閱讀,理解。沒有很好的表現(xiàn)出意圖。
以上討論了系統(tǒng)構(gòu)架的臭味,下面討論微觀層次上代碼的臭味:
代碼的臭味
重復(fù)代碼:重復(fù)的代碼使得更改功能和排錯更加困難。同樣的模塊錯誤會在拷貝粘貼的程序各處多次出現(xiàn)。
過長的函數(shù):程序越長越難于理解,這已經(jīng)是軟件業(yè)開發(fā)的常識。越難理解的程序,使用維護(hù)的成本就越大。如果一個函數(shù)的行數(shù)超過一頁,很少有人能夠在看到下一頁的時候還清楚的記得函數(shù)開頭的變量定義,理解和查錯更加困難。
過大類:在一個類中完成幾乎所有需要的功能。十項(xiàng)全能的人是不存在的,軟件也一樣。
過長的參數(shù)列:如果一個函數(shù)(方法)的調(diào)用參數(shù)過長,使用這個函數(shù)的調(diào)用過程也一定是困難的。想象一下,調(diào)用一個十個以上參數(shù)存儲過程會有多么痛苦。這還只是開始,如果任一個參數(shù)的定義(名稱,類型)發(fā)生輕微的變化,函數(shù)的調(diào)用客戶端會有多么大的改動。
其他的臭味還有發(fā)散式變化,散彈槍修改,依戀情結(jié),數(shù)據(jù)泥團(tuán),基本型別偏執(zhí),復(fù)雜的
switch
分支語句,平行的繼承體系,冗贅類,夸夸其談的未來性,令人迷惑的暫時值域,過度耦合的消息鏈,中間轉(zhuǎn)手人,狎昵關(guān)系,異曲同工的類,不完美的程序庫類,純數(shù)據(jù)類(數(shù)據(jù)啞元),子類不需要父類的某些特性,過多注釋。詳細(xì)的討論可以參見《重構(gòu)》的介紹。
面向?qū)ο筌浖O(shè)計(jì)的原則
:
總體原則
:
1.?
針對于接口(抽象)編程,而不要針對于實(shí)現(xiàn)(具體)編程。
舉例來說:操作系統(tǒng)是對邏輯計(jì)算機(jī)的抽象,通過操作系統(tǒng)的抽象我們不需要考慮具體使用的硬件配置,可以在較高的層次上進(jìn)行更高生產(chǎn)力的應(yīng)用。再如:匯編語言對機(jī)器的
0
,
1
代碼進(jìn)行了抽象,大大加快了開發(fā)效率,后來使用的高級語言和第四代語言模型驅(qū)動抽象的級別更高,生產(chǎn)力也更高。再如:
java
和
.net
實(shí)現(xiàn)于一個抽象的軟件虛擬機(jī),進(jìn)一步使開發(fā)出來的組件可以跨平臺和操作系統(tǒng)。通過抽象出數(shù)據(jù)訪問層(持久化層),可以使業(yè)務(wù)邏輯和具體的數(shù)據(jù)庫訪問代碼分離,更換數(shù)據(jù)庫提供商對已有的組件沒有影響。具體實(shí)現(xiàn)可以參照
hibernate
實(shí)現(xiàn)和
dao
(數(shù)據(jù)訪問對象)模式。
優(yōu)勢:
1
)降低程序各個部分之間的耦合性,使程序模塊互換成為可能。調(diào)用的客戶端無需知道具體使用的對象類型,只要對象有客戶希望的接口就可以使用,對象具體是如何實(shí)現(xiàn)這些接口的,客戶并不需要考慮。
2
)簡化了程序各個部分的單元測試,將需要測試的程序模塊中通過重構(gòu)提煉出抽象的接口,然后編制和接口一致的
Mock
類,測試就會變得很容易。如果應(yīng)用了測試優(yōu)先的方法,從簡化客戶端調(diào)用的角度,還可以經(jīng)過抽象改善軟件模塊的設(shè)計(jì)。
3
)模塊的部署升級由于模塊之間的耦合度降低變得更加容易。
相關(guān)的設(shè)計(jì)模式有創(chuàng)建型模式中的工廠模式,結(jié)構(gòu)型模式中的代理模式和組合模式等。
2.?
對象組合優(yōu)于類繼承。
面向?qū)ο鬄檐浖_發(fā)引入了三大工具:繼承,多態(tài)和重載。繼承使得程序員可以快速的通過擴(kuò)展子類來增加功能,但是由于繼承是在編譯時確定的,因此增加的功能較多時,繼承不夠靈活,還有可能出現(xiàn)“子類爆炸”的局面(為了完成新添功能,不得不在繼承體系中添入大量的之間只有細(xì)微差別的子類,掌握使用擴(kuò)展都會變得非常困難)。而通過對象的組合,可以動態(tài)透明的添加功能。相關(guān)設(shè)計(jì)模式有裝飾模式和代理模式等。
?
3.?
分離變化。前面說過需求是在不斷變化的,不同的變化可能是倉庫安全庫存的計(jì)算方法,可能是報表和數(shù)據(jù)的展現(xiàn)形式,通過把這些不同的變化識別并分離出來不同的對象委托,簡化了客戶端的調(diào)用和升級。相關(guān)的設(shè)計(jì)模式有命令模式,觀察者模式,策略模式,狀態(tài)模式,模版方法模式等。
?
具體原則:
1.?
單一職責(zé)原則
srp
(
single responsibility principle
):一個模塊的功能應(yīng)該盡可能的內(nèi)聚。如果一個類發(fā)生了變化,引起變化的原因應(yīng)該有且只有一個。每一個類承擔(dān)的職責(zé)都是一個變化的軸線,需求變化時,會體現(xiàn)為類的職責(zé)的變化。如果一個類承擔(dān)的職責(zé)過多,就等于把這些職責(zé)耦合在了一起,一個職責(zé)的變化會影響這個類完成其他職責(zé)的能力,會出現(xiàn)前面所說的軟件的臭味之一脆弱性。相關(guān)的設(shè)計(jì)模式有
2.?
開放封閉原則
ocp
(
open closed principle
):一個模塊應(yīng)該對功能的擴(kuò)展開放,支持新的行為,對自身的更改封閉。每次對模塊的修改都可能會引入新的錯誤和新的依賴。因此擴(kuò)展新功能時,已經(jīng)編好的模塊源碼和二進(jìn)制代碼都是不應(yīng)該修改的。相關(guān)的設(shè)計(jì)模式有適配器模式,橋接模式,訪問者模式等。
3.?
Liskov
替換原則
lsp
(
liskov subtitle principle
)子類型必須可以替換掉他的基類型。一個基類的多個子類型之間要完成動態(tài)的替換,各個子類型必須都可以被他們的基類型替換,這樣他們之間動態(tài)替換后,客戶端調(diào)用的代碼就不需要冗贅的
switch
類型判斷代碼。如果
子類型無法替換基類型,將會導(dǎo)致在派生類對象作為基類對象進(jìn)行傳值時的錯誤。這樣多態(tài)機(jī)制處于癱瘓狀態(tài)了。相關(guān)設(shè)計(jì)模式為組合模式。
4.?
依賴倒置原則
dip
(
dependent inverse principle
)高層模塊不應(yīng)該依賴于底層模塊,抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象。假定所有的具體類都是回變化的,因此如果一個客戶端依賴于(調(diào)用或聲明)具體的類型,那么當(dāng)這個具體的類型變化時,依賴的客戶端業(yè)必須同時進(jìn)行修改。這些具體的更改可能出現(xiàn)在使用了某個特定的網(wǎng)絡(luò)協(xié)議,特殊的系統(tǒng)
api
調(diào)用,特定的數(shù)據(jù)庫儲存過程等,這些用法或多或少都會使客戶端調(diào)用和具體類型成為鐵板一塊,比較難于重用。
Java
社區(qū)中比較熱門的
j2ee
輕量級容器框架
spring
就很好的實(shí)現(xiàn)了本原則。
5
.接口隔離原則
isp
(
interface segregation principle
)不應(yīng)該強(qiáng)迫客戶依賴于它們不使用的方法。因?yàn)槊恳粋€實(shí)現(xiàn)接口的對象必須實(shí)現(xiàn)所有接口中定義的方法。如果接口的粒度比較小,實(shí)現(xiàn)接口的對象可以使用一種即用即付的方式動態(tài)實(shí)現(xiàn)接口。每個接口的粒度很小,復(fù)用起來也非常容易。
這體現(xiàn)了一個趨勢:為了更好的實(shí)現(xiàn)重用,接口,函數(shù),模塊和類等傾向于更容易使用的“小”體積。
敏捷軟件開發(fā)的宣言和實(shí)踐:
軟件開發(fā)項(xiàng)目的失敗使得人們開始思考軟件開發(fā)的過程,人們希望通過引入嚴(yán)格的過程控制產(chǎn)生軟件生命周期中各個階段的文檔和制品來保證軟件的質(zhì)量。比較出名的業(yè)界實(shí)施方法論有
cmmi
(能力成熟度模型)和
rup
(瑞理統(tǒng)一過程),這些方法論都是重型的。舉例來說,沒有經(jīng)過剪裁的
Rup
實(shí)現(xiàn)起來,至少需要在全周期完成四十個以上的制品文檔,文檔的編寫維護(hù)和源代碼的同步等需要非常多的資源,十人以下的開發(fā)團(tuán)隊(duì)一般沒有精力、時間、人員配置完成這些制品。失控的過程的膨脹迫使人們尋找一種快速工作,相應(yīng)變化的“敏捷的”方法。敏捷團(tuán)隊(duì)提倡通過團(tuán)隊(duì)成員之間充分有效的溝通統(tǒng)一大家的目標(biāo),結(jié)伴的方式完成開發(fā)技術(shù)的團(tuán)隊(duì)內(nèi)傳承,使用“夠用就好”的輕量甚至免費(fèi)的工具管理過程。可以正常工作的代碼擺在首要地位,只有必要的時候才生產(chǎn)必要的文檔。強(qiáng)調(diào)和客戶面對面地交流合作,積極地響應(yīng)客戶需求的變化而不是遵循機(jī)械的計(jì)劃。使用較短的迭代周期,近早和持續(xù)提交有價值的軟件給客戶來驗(yàn)證并修正和用戶需求的吻合程度。提倡可以持續(xù)的穩(wěn)定的開發(fā)節(jié)奏,長期“小步快走”的方式代替突然的“百米沖刺”。保持設(shè)計(jì)最優(yōu),最簡單的設(shè)計(jì)并且持續(xù)改進(jìn),不斷調(diào)整。