--第三章 函數(shù)
函數(shù)只做一件事情,要盡量短小
編寫函數(shù)畢竟是為了把大一些的概念拆分為另一個抽象層上的一系列步驟
要判斷函數(shù)是否不止做了一件事,還有一個方法,就是看是否能再拆除一個函數(shù),該函數(shù)不僅只是單純地重新詮釋其實現(xiàn)(需要改變抽象層級)
每個函數(shù)一個抽象層級
遵循自頂向下的編寫規(guī)則:程序就像是一些列TO起頭的段落,每一段都描述當(dāng)前抽象層級,并引用位于下一抽象層級的后續(xù)TO起頭段落
switch語句,盡量確保每個switch都埋藏在較低的抽象層級,而且永遠不重復(fù)
函數(shù)要盡量使用描述性的名稱,描述函數(shù)所做的事情
沃德原則:如果每個例程都讓你感到深合己意,那就是整潔代碼
函數(shù)越短小、功能越集中,就越便于取個好名字
函數(shù)參數(shù):最理想的參數(shù)數(shù)量是零(零參數(shù)函數(shù)),其次是一(單參數(shù)函數(shù)),再次是二(雙參數(shù)函數(shù)),應(yīng)盡量避免三(三參數(shù)函數(shù)),有足夠特殊的理由才能使用三個以上參數(shù)(多參數(shù)函數(shù))
普遍而言,應(yīng)避免使用輸出參數(shù)。如果函數(shù)必須要修改某種狀態(tài),就修改所屬對象的狀態(tài)吧
例子:public void appendFooter(StringBuffer report) 調(diào)整成
report.appendFooter() 是不是更好理解
分隔指令與詢問:函數(shù)要么做什么事,要么回答什么事,但二者不可兼得。函數(shù)應(yīng)該修改某對象的狀態(tài),或是返回該對象的有關(guān)信息。如setXXX對應(yīng)設(shè)置對象屬性,不可有返回參數(shù),isXXX應(yīng)該返回boolean類型的參數(shù)
使用異常替代返回的錯誤碼,即使用try/catch,但是錯誤處理就是一件事情,只做一件事情
例子:
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());
}
進一步模塊化:
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ù)可能是軟件中一切邪惡的根源。許多原則與實踐規(guī)則都是為控制與消除重復(fù)而創(chuàng)建的??蓞⒖即a清單3.1和代碼清單3.7
如何寫出完美的函數(shù):寫代碼和寫文章很想,先想寫什么就寫什么,然后再打磨它,初稿也許粗陋無需,就需要斟酌推敲,直至達到你心目中的樣子。并不是從一開始就按照規(guī)則寫函數(shù),需要打磨,需要分解、修改名稱、消除重復(fù),有時還拆散類,同事保持測試通過,最后遵循函數(shù)編寫的規(guī)則,組裝好這些函數(shù)。
小結(jié):每個系統(tǒng)都是使用某種領(lǐng)域特定語言搭建,而這種語言是程序員設(shè)計來描述這個系統(tǒng)的。函數(shù)是語言的動詞,類是名詞,大師級程序員把系統(tǒng)當(dāng)做故事來講,而不是當(dāng)做程序來寫,真正的目標(biāo)在于講述系統(tǒng)的故事,而編寫的函數(shù)必須干凈利落地拼裝在一起,形成一種精確而清晰的語言,幫助我們講故事。參考代碼清單3-7
--第四章 注釋
注釋不能美化糟糕的代碼,盡管有時也需要注釋,我們也該多花心思盡量減少注釋量
用代碼來闡述,很多時候,簡單到只需要創(chuàng)建一個描述與注釋所言同一事物的函數(shù)即可
TODO 是一種程序員認(rèn)為應(yīng)該做,但由于某些原因目前還沒做的工作??赡苁且嵝涯硞€不必要的特性,或者要求他人注意某個問題,無論目的如何,它都不是在系統(tǒng)中留下糟糕的代碼的借口
JAVADOC 如果你再編寫公共API,就該為它編寫良好的Javadoc
應(yīng)該避免 喃喃自語 多余的注釋 誤導(dǎo)性注釋 循規(guī)式注釋 日志式注釋 廢話注釋
范例 參考對比 代碼清單4-7 與 代碼清單4-8
--第六章 對象和數(shù)據(jù)結(jié)構(gòu)
數(shù)據(jù)抽象 隱藏實現(xiàn)并非只是在變量之間放上一個函數(shù)層那么簡單。隱藏實現(xiàn)關(guān)乎抽象,類并不簡單地用取值器和賦值器將其變量推向外間,而是暴露抽象接口,以便用戶無需了解數(shù)據(jù)的實現(xiàn)就能操作數(shù)據(jù)本體
例子:
具象機動車
public interface Vehicle {
????double getFuleTankCapacityInGallons();
????double getGallonsOfGasoline();
}
抽象機動車
public interface Vehicle {
????double getPercentFuelRemaining();
}
數(shù)據(jù)、對象的反對稱性 對象把數(shù)據(jù)隱藏于抽象之后,暴露操作數(shù)據(jù)的函數(shù);數(shù)據(jù)結(jié)構(gòu)暴露其數(shù)據(jù),沒有提供有意義的函數(shù)。
對象與數(shù)據(jù)結(jié)構(gòu)之間存在二分原理:過程式代碼(使用數(shù)據(jù)結(jié)構(gòu)的代碼)便于在不改動既有數(shù)據(jù)結(jié)構(gòu)的前提下添加新函數(shù)。面向?qū)ο蟠a便于在不改動既有函數(shù)的前提下添加新類。過程式代碼難以添加新數(shù)據(jù)結(jié)構(gòu),因為必須修改所有函數(shù)。面向?qū)ο蟠a難以添加新函數(shù),因為必須修改所有類。(例子見6.2 數(shù)據(jù)、對象的反對稱性 代碼清單6-5 6-6)
迪米特法則 模塊不應(yīng)了解它所操作對象的內(nèi)部細(xì)節(jié)。意味著對象不應(yīng)通過存取器暴露其內(nèi)部結(jié)構(gòu),因為這樣更像是暴露而非隱藏其內(nèi)部結(jié)構(gòu)。
例子:類C的方法f只應(yīng)該調(diào)用以下對象的方法:
C
由f創(chuàng)建的對象
作為參數(shù)傳遞給f的對象
由C的實體變量持有的對象
當(dāng)出現(xiàn)對象需要直接暴露數(shù)據(jù)的時候,可以去探究要直接暴露數(shù)據(jù)的目的,清楚目的后,通過模塊化隱藏掉細(xì)節(jié),暴露操作數(shù)據(jù)的方法
對象暴露行為,隱藏數(shù)據(jù),便于添加新對象類型而無需修改既有行為,同時也難以在既有對象中添加新行為。
數(shù)據(jù)結(jié)構(gòu)暴露數(shù)據(jù),沒有明顯的行為。便于向既有數(shù)據(jù)結(jié)構(gòu)添加新行為,同時也難以向既有函數(shù)添加新數(shù)據(jù)結(jié)構(gòu)。
在任何系統(tǒng)中,我們有時會希望能夠靈活地添加新數(shù)據(jù)類型,所以更喜歡在這部分使用對象。另一些時候,我們希望能靈活地添加新行為,這時我們更喜歡使用數(shù)據(jù)類型和過程。優(yōu)秀的軟件開發(fā)者不帶成見地了解這種情形,并依據(jù)手邊工作的性質(zhì)選擇其中一種手段。
數(shù)據(jù)結(jié)構(gòu)指的就是數(shù)據(jù)的載體,暴露數(shù)據(jù),而幾乎沒有有意義的行為的貧血類。最常見的應(yīng)用在分布式服務(wù),以wcf,webservice,reset之類的分布式服務(wù)中不可或缺的數(shù)據(jù)傳輸對象(DTO)模式,DTO(Request/Response)就是一個很典型的數(shù)據(jù)載體,只存在簡單的get,set屬性,并且更傾向于作為值對象存在。而對象則剛好相反作為面向?qū)ο蟮漠a(chǎn)物,必須封裝隱藏數(shù)據(jù),而暴露出行為接口,DDD中領(lǐng)域模型傾向于對象不僅在數(shù)據(jù)更多暴露行為操作自己或者關(guān)聯(lián)狀態(tài)。
數(shù)據(jù)結(jié)構(gòu)和對象之間看是細(xì)微的差別卻導(dǎo)致了不同的本質(zhì)區(qū)別:使用數(shù)據(jù)結(jié)構(gòu)的代碼便于在不改動現(xiàn)在數(shù)據(jù)結(jié)構(gòu)的前提下添加新的行為(函數(shù)),面向?qū)ο蟠a則便于不改動現(xiàn)有函數(shù)的前提下添加新的類。換句話說就是數(shù)據(jù)結(jié)構(gòu)難以添加新的的數(shù)據(jù)類型,因為需要改動所有函數(shù),面向?qū)ο蟮拇a則難以添加新的函數(shù),因為需要修改所有的類。在任何一個復(fù)雜的系統(tǒng)都會同時存在數(shù)據(jù)結(jié)構(gòu)和對象,我們需要判斷的是我們需要的是需要添加的新的數(shù)據(jù)類型還是新的行為函數(shù)。
隱藏作為面向?qū)ο笾饕匦灾械淖钪匾匦?,封裝隱藏是面向?qū)ο笾凶钪匾奶匦?,一個好的面向?qū)ο蟠a肯定是對對象的內(nèi)部細(xì)節(jié)做到很好的隱藏封裝,封裝過后才有是多態(tài),委派之類的。一個好的面向?qū)ο蟮拇a一定是具有很好的隱藏封裝,易于測試,不穩(wěn)定因素往往集中在一處很小或者固定的位置,不穩(wěn)定因素的變更不會導(dǎo)致更大面積的修改擴散。
對象的隱藏要求:方法不應(yīng)和任何調(diào)用方法返回的對象操作,換句話之和朋友說話,不和陌生人說話(迪米特法則,或被譯為最小知識原則),比如:ctxt.getOptions().getSearchDir().getAbsolutePath(),就是迪米特法則的反例模式。
--第七章 錯誤處理
使用異常而非返回錯誤碼 會使代碼變得整潔,使錯誤處理與算法隔離開,在遇到錯誤時,最好拋出一個異常
先寫Try-catch-finally語句 最好先寫出該語句,這能幫你定義代碼的用戶應(yīng)該期待什么,無論try代碼塊中執(zhí)行的代碼出什么錯都一樣
使用不可控異常 使用可控異常會違反開閉原則,在中低層的修改,會涉及到高層的簽名,所以如果在編寫一套關(guān)鍵代碼庫,則可控異常有時會很有用:必須捕獲異常,但對于一般應(yīng)用的開發(fā),其依賴成本要高于收益
依調(diào)用者需要定義異常類 我們在應(yīng)用程序中定義異常類時,最重要的考慮應(yīng)該是它們是如何被捕獲的。但是在做異常分類的時候可能會包含一堆重復(fù)的異常處理代碼(例子:7.5)
可以通過 打包API(做一層封裝) 的方式,確保其異常類型,從而簡化代碼。(參考7.5)
定義常規(guī)流程 在業(yè)務(wù)邏輯和錯誤處理代碼之間設(shè)置好了隔閡,這樣把錯誤檢測推到了程序的邊緣地帶(打包API的方式,在代碼頂端定義處理器來應(yīng)付任何失敗的運算),但也存在特例(參考7.6)
使用特例模式,創(chuàng)建一個或者配置一個對象,用來處理特例;在接口中使用多態(tài)的方式進入到特例中,這樣外部程序不需要調(diào)整,只要調(diào)整內(nèi)部行為即可(參考7.6)
別返回NULL值 不要返回NULL值,如果實例化對象失敗,建議也要創(chuàng)建默認(rèn)實例。因為程序中如果沒有判斷null值,就會奔潰,即使對null值處理也會造成代碼不整潔,可使用特例模式避免NULL值返回
別傳遞NULL值 除非API要求你向他傳遞null值,否則就要盡可能避免傳遞null值
整潔的代碼是可讀的,但也要強固,兩者并不沖突,如果將錯誤隔離看待,獨立于主要邏輯之外,就能寫出強固而整潔的代碼
第九章 單元測試
TDD三大定律
1.在編寫不能通過的單元測試前不可編寫生產(chǎn)代碼;
2.只可編寫剛好通過得單元測試,不能編譯也算不通過;
3.只可編寫剛好足以通過當(dāng)前失敗測試的生產(chǎn)代碼
保持測試整潔 測試代碼和生產(chǎn)代碼一樣重要。它需要被思考、被設(shè)計和被照料。它該項生產(chǎn)代碼一般保持整潔。
整潔的測試 可讀性、可讀性和可讀性,以盡可能少的文字表達大量內(nèi)容,明確、簡潔,還有足夠的表達力。
構(gòu)造-操作-檢驗(BUILD-OPERATE-CHECK)測試代碼三個步驟
F.I.R.S.T 整潔的測試遵循以下5條規(guī)則:
1.快速(Fast) 測試應(yīng)該能快速運行,頻繁運行測試,就能今早發(fā)現(xiàn)問題
2.獨立(Independent)測試應(yīng)該相互獨立。某個測試不應(yīng)為下一個測試設(shè)定條件,可以單獨運行每個測試,及以任務(wù)順序運行測試。
3.可重復(fù)(Repeatable)測試應(yīng)該可在任務(wù)環(huán)境中重復(fù)通過。
4.自足驗證(Self-Validating)測試應(yīng)該有布爾值輸出。不應(yīng)該手工對比量不同文件來確認(rèn)測試是否通過。
5.及時(Timely)測試應(yīng)該及時編寫。單元測試應(yīng)該恰好在使其通過的生產(chǎn)代碼之前編寫,如果在編寫生產(chǎn)代碼之后編寫測試,你會發(fā)現(xiàn)生產(chǎn)代碼難以測試。