--第三章 函數(shù)
函數(shù)只做一件事情膀哲,要盡量短小
編寫(xiě)函數(shù)畢竟是為了把大一些的概念拆分為另一個(gè)抽象層上的一系列步驟
要判斷函數(shù)是否不止做了一件事脯颜,還有一個(gè)方法份名,就是看是否能再拆除一個(gè)函數(shù)菠赚,該函數(shù)不僅只是單純地重新詮釋其實(shí)現(xiàn)(需要改變抽象層級(jí))
每個(gè)函數(shù)一個(gè)抽象層級(jí)
遵循自頂向下的編寫(xiě)規(guī)則:程序就像是一些列TO起頭的段落驳阎,每一段都描述當(dāng)前抽象層級(jí)抗愁,并引用位于下一抽象層級(jí)的后續(xù)TO起頭段落
switch語(yǔ)句馁蒂,盡量確保每個(gè)switch都埋藏在較低的抽象層級(jí),而且永遠(yuǎn)不重復(fù)
函數(shù)要盡量使用描述性的名稱(chēng)蜘腌,描述函數(shù)所做的事情
沃德原則:如果每個(gè)例程都讓你感到深合己意沫屡,那就是整潔代碼
函數(shù)越短小、功能越集中撮珠,就越便于取個(gè)好名字
函數(shù)參數(shù):最理想的參數(shù)數(shù)量是零(零參數(shù)函數(shù))沮脖,其次是一(單參數(shù)函數(shù)),再次是二(雙參數(shù)函數(shù))芯急,應(yīng)盡量避免三(三參數(shù)函數(shù))勺届,有足夠特殊的理由才能使用三個(gè)以上參數(shù)(多參數(shù)函數(shù))
普遍而言,應(yīng)避免使用輸出參數(shù)娶耍。如果函數(shù)必須要修改某種狀態(tài)免姿,就修改所屬對(duì)象的狀態(tài)吧
例子:public void appendFooter(StringBuffer report) 調(diào)整成
report.appendFooter() 是不是更好理解
分隔指令與詢(xún)問(wèn):函數(shù)要么做什么事,要么回答什么事榕酒,但二者不可兼得胚膊。函數(shù)應(yīng)該修改某對(duì)象的狀態(tài)佃迄,或是返回該對(duì)象的有關(guān)信息巫俺。如setXXX對(duì)應(yīng)設(shè)置對(duì)象屬性梅猿,不可有返回參數(shù)米奸,isXXX應(yīng)該返回boolean類(lèi)型的參數(shù)
使用異常替代返回的錯(cuò)誤碼,即使用try/catch荧止,但是錯(cuò)誤處理就是一件事情塘雳,只做一件事情
例子:
if (deletePage(page) == E_OK) {
if (registry.deleteReference(page.name) == E_OK) {
????if (configKeys.deleteKey(page.name.makeKey()) == E_OK) {
? ? ? ? ?logger.log("page delete");
????} else {
????????logger.log("configKeys not delete");
????}
} else {
? ? ? ?logger.log("deleteReference from registry failed");
}
} else {
????logger.log("delete failed");
????return E_ERROR;
}
調(diào)整為:
try {
????deletePage(page);
????registry.deleteReference(page.name);
????configKeys.deleteKey(page.name.makeKey();
} catch(Exception e) {
????logger.log(e.getMessage());
}
進(jìn)一步模塊化:
public void delete(page) {
????try {
????????deletePageAndAllReferences(page);
????} catch(Exception e) {
????????logError(e);
????}
}
private void deletePageAndAllReferences(Page page) throws Exception {
????deletePage(page);
????registry.deleteReference(page.name);
????configKeys.deleteKey(page.name.makeKey();
}
private void logError(e) {
????logger.log(e.getMessage());
}
不要重復(fù)自己:重復(fù)可能是軟件中一切邪惡的根源。許多原則與實(shí)踐規(guī)則都是為控制與消除重復(fù)而創(chuàng)建的惩妇。可參考代碼清單3.1和代碼清單3.7
如何寫(xiě)出完美的函數(shù):寫(xiě)代碼和寫(xiě)文章很想筐乳,先想寫(xiě)什么就寫(xiě)什么歌殃,然后再打磨它,初稿也許粗陋無(wú)需蝙云,就需要斟酌推敲氓皱,直至達(dá)到你心目中的樣子。并不是從一開(kāi)始就按照規(guī)則寫(xiě)函數(shù)勃刨,需要打磨波材,需要分解、修改名稱(chēng)身隐、消除重復(fù)廷区,有時(shí)還拆散類(lèi),同事保持測(cè)試通過(guò)贾铝,最后遵循函數(shù)編寫(xiě)的規(guī)則隙轻,組裝好這些函數(shù)埠帕。
小結(jié):每個(gè)系統(tǒng)都是使用某種領(lǐng)域特定語(yǔ)言搭建,而這種語(yǔ)言是程序員設(shè)計(jì)來(lái)描述這個(gè)系統(tǒng)的玖绿。函數(shù)是語(yǔ)言的動(dòng)詞敛瓷,類(lèi)是名詞,大師級(jí)程序員把系統(tǒng)當(dāng)做故事來(lái)講斑匪,而不是當(dāng)做程序來(lái)寫(xiě)呐籽,真正的目標(biāo)在于講述系統(tǒng)的故事,而編寫(xiě)的函數(shù)必須干凈利落地拼裝在一起蚀瘸,形成一種精確而清晰的語(yǔ)言绝淡,幫助我們講故事。參考代碼清單3-7
--第四章 注釋
注釋不能美化糟糕的代碼苍姜,盡管有時(shí)也需要注釋?zhuān)覀円苍摱嗷ㄐ乃急M量減少注釋量
用代碼來(lái)闡述牢酵,很多時(shí)候,簡(jiǎn)單到只需要?jiǎng)?chuàng)建一個(gè)描述與注釋所言同一事物的函數(shù)即可
TODO 是一種程序員認(rèn)為應(yīng)該做衙猪,但由于某些原因目前還沒(méi)做的工作馍乙。可能是要提醒某個(gè)不必要的特性垫释,或者要求他人注意某個(gè)問(wèn)題丝格,無(wú)論目的如何,它都不是在系統(tǒng)中留下糟糕的代碼的借口
JAVADOC 如果你再編寫(xiě)公共API棵譬,就該為它編寫(xiě)良好的Javadoc
應(yīng)該避免 喃喃自語(yǔ) 多余的注釋 誤導(dǎo)性注釋 循規(guī)式注釋 日志式注釋 廢話注釋
范例 參考對(duì)比 代碼清單4-7 與 代碼清單4-8
--第六章 對(duì)象和數(shù)據(jù)結(jié)構(gòu)
數(shù)據(jù)抽象 隱藏實(shí)現(xiàn)并非只是在變量之間放上一個(gè)函數(shù)層那么簡(jiǎn)單显蝌。隱藏實(shí)現(xiàn)關(guān)乎抽象,類(lèi)并不簡(jiǎn)單地用取值器和賦值器將其變量推向外間订咸,而是暴露抽象接口曼尊,以便用戶無(wú)需了解數(shù)據(jù)的實(shí)現(xiàn)就能操作數(shù)據(jù)本體
例子:
具象機(jī)動(dòng)車(chē)
public interface Vehicle {
????double getFuleTankCapacityInGallons();
????double getGallonsOfGasoline();
}
抽象機(jī)動(dòng)車(chē)
public interface Vehicle {
????double getPercentFuelRemaining();
}
數(shù)據(jù)、對(duì)象的反對(duì)稱(chēng)性 對(duì)象把數(shù)據(jù)隱藏于抽象之后脏嚷,暴露操作數(shù)據(jù)的函數(shù)骆撇;數(shù)據(jù)結(jié)構(gòu)暴露其數(shù)據(jù),沒(méi)有提供有意義的函數(shù)父叙。
對(duì)象與數(shù)據(jù)結(jié)構(gòu)之間存在二分原理:過(guò)程式代碼(使用數(shù)據(jù)結(jié)構(gòu)的代碼)便于在不改動(dòng)既有數(shù)據(jù)結(jié)構(gòu)的前提下添加新函數(shù)神郊。面向?qū)ο蟠a便于在不改動(dòng)既有函數(shù)的前提下添加新類(lèi)。過(guò)程式代碼難以添加新數(shù)據(jù)結(jié)構(gòu)趾唱,因?yàn)楸仨毿薷乃泻瘮?shù)涌乳。面向?qū)ο蟠a難以添加新函數(shù),因?yàn)楸仨毿薷乃蓄?lèi)甜癞。(例子見(jiàn)6.2 數(shù)據(jù)夕晓、對(duì)象的反對(duì)稱(chēng)性 代碼清單6-5 6-6)
迪米特法則 模塊不應(yīng)了解它所操作對(duì)象的內(nèi)部細(xì)節(jié)。意味著對(duì)象不應(yīng)通過(guò)存取器暴露其內(nèi)部結(jié)構(gòu)带欢,因?yàn)檫@樣更像是暴露而非隱藏其內(nèi)部結(jié)構(gòu)运授。
例子:類(lèi)C的方法f只應(yīng)該調(diào)用以下對(duì)象的方法:
C
由f創(chuàng)建的對(duì)象
作為參數(shù)傳遞給f的對(duì)象
由C的實(shí)體變量持有的對(duì)象
當(dāng)出現(xiàn)對(duì)象需要直接暴露數(shù)據(jù)的時(shí)候烤惊,可以去探究要直接暴露數(shù)據(jù)的目的,清楚目的后吁朦,通過(guò)模塊化隱藏掉細(xì)節(jié)柒室,暴露操作數(shù)據(jù)的方法
對(duì)象暴露行為,隱藏?cái)?shù)據(jù)逗宜,便于添加新對(duì)象類(lèi)型而無(wú)需修改既有行為雄右,同時(shí)也難以在既有對(duì)象中添加新行為。
數(shù)據(jù)結(jié)構(gòu)暴露數(shù)據(jù)纺讲,沒(méi)有明顯的行為擂仍。便于向既有數(shù)據(jù)結(jié)構(gòu)添加新行為,同時(shí)也難以向既有函數(shù)添加新數(shù)據(jù)結(jié)構(gòu)熬甚。
在任何系統(tǒng)中逢渔,我們有時(shí)會(huì)希望能夠靈活地添加新數(shù)據(jù)類(lèi)型,所以更喜歡在這部分使用對(duì)象乡括。另一些時(shí)候肃廓,我們希望能靈活地添加新行為,這時(shí)我們更喜歡使用數(shù)據(jù)類(lèi)型和過(guò)程诲泌。優(yōu)秀的軟件開(kāi)發(fā)者不帶成見(jiàn)地了解這種情形盲赊,并依據(jù)手邊工作的性質(zhì)選擇其中一種手段。
數(shù)據(jù)結(jié)構(gòu)指的就是數(shù)據(jù)的載體敷扫,暴露數(shù)據(jù)哀蘑,而幾乎沒(méi)有有意義的行為的貧血類(lèi)。最常見(jiàn)的應(yīng)用在分布式服務(wù)葵第,以wcf绘迁,webservice,reset之類(lèi)的分布式服務(wù)中不可或缺的數(shù)據(jù)傳輸對(duì)象(DTO)模式羹幸,DTO(Request/Response)就是一個(gè)很典型的數(shù)據(jù)載體脊髓,只存在簡(jiǎn)單的get,set屬性栅受,并且更傾向于作為值對(duì)象存在。而對(duì)象則剛好相反作為面向?qū)ο蟮漠a(chǎn)物恭朗,必須封裝隱藏?cái)?shù)據(jù)屏镊,而暴露出行為接口,DDD中領(lǐng)域模型傾向于對(duì)象不僅在數(shù)據(jù)更多暴露行為操作自己或者關(guān)聯(lián)狀態(tài)痰腮。
數(shù)據(jù)結(jié)構(gòu)和對(duì)象之間看是細(xì)微的差別卻導(dǎo)致了不同的本質(zhì)區(qū)別:使用數(shù)據(jù)結(jié)構(gòu)的代碼便于在不改動(dòng)現(xiàn)在數(shù)據(jù)結(jié)構(gòu)的前提下添加新的行為(函數(shù))而芥,面向?qū)ο蟠a則便于不改動(dòng)現(xiàn)有函數(shù)的前提下添加新的類(lèi)。換句話說(shuō)就是數(shù)據(jù)結(jié)構(gòu)難以添加新的的數(shù)據(jù)類(lèi)型膀值,因?yàn)樾枰膭?dòng)所有函數(shù)棍丐,面向?qū)ο蟮拇a則難以添加新的函數(shù)误辑,因?yàn)樾枰薷乃械念?lèi)。在任何一個(gè)復(fù)雜的系統(tǒng)都會(huì)同時(shí)存在數(shù)據(jù)結(jié)構(gòu)和對(duì)象歌逢,我們需要判斷的是我們需要的是需要添加的新的數(shù)據(jù)類(lèi)型還是新的行為函數(shù)巾钉。
隱藏作為面向?qū)ο笾饕匦灾械淖钪匾匦裕庋b隱藏是面向?qū)ο笾凶钪匾奶匦悦匕福粋€(gè)好的面向?qū)ο蟠a肯定是對(duì)對(duì)象的內(nèi)部細(xì)節(jié)做到很好的隱藏封裝砰苍,封裝過(guò)后才有是多態(tài),委派之類(lèi)的阱高。一個(gè)好的面向?qū)ο蟮拇a一定是具有很好的隱藏封裝赚导,易于測(cè)試,不穩(wěn)定因素往往集中在一處很小或者固定的位置赤惊,不穩(wěn)定因素的變更不會(huì)導(dǎo)致更大面積的修改擴(kuò)散吼旧。
對(duì)象的隱藏要求:方法不應(yīng)和任何調(diào)用方法返回的對(duì)象操作,換句話之和朋友說(shuō)話未舟,不和陌生人說(shuō)話(迪米特法則圈暗,或被譯為最小知識(shí)原則),比如:ctxt.getOptions().getSearchDir().getAbsolutePath()处面,就是迪米特法則的反例模式厂置。
--第七章 錯(cuò)誤處理
使用異常而非返回錯(cuò)誤碼 會(huì)使代碼變得整潔,使錯(cuò)誤處理與算法隔離開(kāi)魂角,在遇到錯(cuò)誤時(shí)昵济,最好拋出一個(gè)異常
先寫(xiě)Try-catch-finally語(yǔ)句 最好先寫(xiě)出該語(yǔ)句,這能幫你定義代碼的用戶應(yīng)該期待什么野揪,無(wú)論try代碼塊中執(zhí)行的代碼出什么錯(cuò)都一樣
使用不可控異常 使用可控異常會(huì)違反開(kāi)閉原則访忿,在中低層的修改,會(huì)涉及到高層的簽名斯稳,所以如果在編寫(xiě)一套關(guān)鍵代碼庫(kù)海铆,則可控異常有時(shí)會(huì)很有用:必須捕獲異常,但對(duì)于一般應(yīng)用的開(kāi)發(fā)挣惰,其依賴(lài)成本要高于收益
依調(diào)用者需要定義異常類(lèi) 我們?cè)趹?yīng)用程序中定義異常類(lèi)時(shí)卧斟,最重要的考慮應(yīng)該是它們是如何被捕獲的。但是在做異常分類(lèi)的時(shí)候可能會(huì)包含一堆重復(fù)的異常處理代碼(例子:7.5)
可以通過(guò) 打包API(做一層封裝) 的方式憎茂,確保其異常類(lèi)型珍语,從而簡(jiǎn)化代碼。(參考7.5)
定義常規(guī)流程 在業(yè)務(wù)邏輯和錯(cuò)誤處理代碼之間設(shè)置好了隔閡竖幔,這樣把錯(cuò)誤檢測(cè)推到了程序的邊緣地帶(打包API的方式板乙,在代碼頂端定義處理器來(lái)應(yīng)付任何失敗的運(yùn)算),但也存在特例(參考7.6)
使用特例模式拳氢,創(chuàng)建一個(gè)或者配置一個(gè)對(duì)象募逞,用來(lái)處理特例蛋铆;在接口中使用多態(tài)的方式進(jìn)入到特例中,這樣外部程序不需要調(diào)整放接,只要調(diào)整內(nèi)部行為即可(參考7.6)
別返回NULL值 不要返回NULL值刺啦,如果實(shí)例化對(duì)象失敗,建議也要?jiǎng)?chuàng)建默認(rèn)實(shí)例透乾。因?yàn)槌绦蛑腥绻麤](méi)有判斷null值洪燥,就會(huì)奔潰,即使對(duì)null值處理也會(huì)造成代碼不整潔乳乌,可使用特例模式避免NULL值返回
別傳遞NULL值 除非API要求你向他傳遞null值捧韵,否則就要盡可能避免傳遞null值
整潔的代碼是可讀的,但也要強(qiáng)固汉操,兩者并不沖突再来,如果將錯(cuò)誤隔離看待,獨(dú)立于主要邏輯之外磷瘤,就能寫(xiě)出強(qiáng)固而整潔的代碼
第九章 單元測(cè)試
TDD三大定律
1.在編寫(xiě)不能通過(guò)的單元測(cè)試前不可編寫(xiě)生產(chǎn)代碼芒篷;
2.只可編寫(xiě)剛好通過(guò)得單元測(cè)試,不能編譯也算不通過(guò)采缚;
3.只可編寫(xiě)剛好足以通過(guò)當(dāng)前失敗測(cè)試的生產(chǎn)代碼
保持測(cè)試整潔 測(cè)試代碼和生產(chǎn)代碼一樣重要针炉。它需要被思考、被設(shè)計(jì)和被照料扳抽。它該項(xiàng)生產(chǎn)代碼一般保持整潔篡帕。
整潔的測(cè)試 可讀性、可讀性和可讀性贸呢,以盡可能少的文字表達(dá)大量?jī)?nèi)容镰烧,明確、簡(jiǎn)潔楞陷,還有足夠的表達(dá)力怔鳖。
構(gòu)造-操作-檢驗(yàn)(BUILD-OPERATE-CHECK)測(cè)試代碼三個(gè)步驟
F.I.R.S.T 整潔的測(cè)試遵循以下5條規(guī)則:
1.快速(Fast) 測(cè)試應(yīng)該能快速運(yùn)行,頻繁運(yùn)行測(cè)試固蛾,就能今早發(fā)現(xiàn)問(wèn)題
2.獨(dú)立(Independent)測(cè)試應(yīng)該相互獨(dú)立结执。某個(gè)測(cè)試不應(yīng)為下一個(gè)測(cè)試設(shè)定條件,可以單獨(dú)運(yùn)行每個(gè)測(cè)試艾凯,及以任務(wù)順序運(yùn)行測(cè)試昌犹。
3.可重復(fù)(Repeatable)測(cè)試應(yīng)該可在任務(wù)環(huán)境中重復(fù)通過(guò)。
4.自足驗(yàn)證(Self-Validating)測(cè)試應(yīng)該有布爾值輸出览芳。不應(yīng)該手工對(duì)比量不同文件來(lái)確認(rèn)測(cè)試是否通過(guò)。
5.及時(shí)(Timely)測(cè)試應(yīng)該及時(shí)編寫(xiě)鸿竖。單元測(cè)試應(yīng)該恰好在使其通過(guò)的生產(chǎn)代碼之前編寫(xiě)沧竟,如果在編寫(xiě)生產(chǎn)代碼之后編寫(xiě)測(cè)試铸敏,你會(huì)發(fā)現(xiàn)生產(chǎn)代碼難以測(cè)試。