設(shè)計(jì)模式之美-設(shè)計(jì)原則、規(guī)范與重構(gòu)_第1頁
設(shè)計(jì)模式之美-設(shè)計(jì)原則、規(guī)范與重構(gòu)_第2頁
設(shè)計(jì)模式之美-設(shè)計(jì)原則、規(guī)范與重構(gòu)_第3頁
設(shè)計(jì)模式之美-設(shè)計(jì)原則、規(guī)范與重構(gòu)_第4頁
設(shè)計(jì)模式之美-設(shè)計(jì)原則、規(guī)范與重構(gòu)_第5頁
已閱讀5頁,還剩4頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

1、設(shè)計(jì)模式之美(一)時時設(shè)計(jì)原則、規(guī)范與重構(gòu)是極客時間上的一個代碼學(xué)習(xí)系列,在學(xué)習(xí)之后特在此做記錄和總結(jié)。、設(shè)計(jì)原則單一職責(zé)原則(,)是指一個類或者模塊只負(fù)責(zé)完成一個職責(zé)(或者功能),模塊可看作比類更加粗粒度的代碼塊,模塊中包含多個類,多個類組成一個模塊。個類包含了兩個或者兩個以上業(yè)務(wù)不相干的功能,那就說它職責(zé)不夠單一,應(yīng)該將它拆分成多個功能更加單一、粒度更細(xì)的類。判斷類的職責(zé)是否足夠單一,需要根據(jù)具體的應(yīng)用場景和階段需求,例如。(1如果在社交產(chǎn)品中,用戶的地址信息只是單純地用來展示,那可包含地址信息。(2如果社交產(chǎn)品中添加了電商模塊,用戶的地址信息還會用在電商物流中,那最好將地址信息從中拆分出

2、來。由此可知,評價一個類的職責(zé)是否足夠單一,并沒有一個非常明確的、可以量化的標(biāo)準(zhǔn)。下面這幾條拆分判斷原則,要更有指導(dǎo)意義、更具有可執(zhí)行性:(1類中的代碼行數(shù)、函數(shù)或?qū)傩赃^多,會影響代碼的可讀性和可維護(hù)性,行數(shù)最好不超過行,函數(shù)個數(shù)及屬性個數(shù)都最好不超過個。(2類依賴的其他類過多,或者依賴類的其他類過多,不符合高內(nèi)聚、低耦合的設(shè)計(jì)思想。(3私有方法過多,就要考慮能否將私有方法獨(dú)立到新的類中,設(shè)置為方法,提高代碼的復(fù)用性。(4比較難給類起一個合適名字,很難用一個業(yè)務(wù)名詞概括,這就說明類的職責(zé)定義得可能不夠清晰。(5類中大量的方法都是集中操作類中的某幾個屬性,那就可以考慮將這幾個屬性和對應(yīng)的方法拆分

3、出來。2)OCP開閉原則(,)是指添加一個新的功能,在已有代碼基礎(chǔ)上擴(kuò)展代碼(新增模塊、類、方法等),而非修改已有代碼(修改模塊、類、方法等)。注意,沒必要糾結(jié)某個代碼改動是修改還是擴(kuò)展,更沒必要太糾結(jié)它是否違反開閉原則。只要沒有破壞原有代碼和單元測試的正常運(yùn)行,就可以說,這是一次合格的代碼改動。不過,有些修改是在所難免的,是可以被接受的。盡量讓修改操作更集中、更少、更上層,盡量讓最核心、最復(fù)雜的那部分邏輯代碼滿足開閉原則。偏向頂層的指導(dǎo)思想:多花點(diǎn)時間思考,這段代碼未來可能有哪些需求變更、如何設(shè)計(jì)代碼結(jié)構(gòu),事先留好擴(kuò)展點(diǎn),以便在未來需求變更的時候,不改動代碼整體結(jié)構(gòu)、做到最小代碼改動的情況下

4、,新的代碼能夠很靈活地插入到擴(kuò)展點(diǎn)上,做到對擴(kuò)展開放、對修改關(guān)閉。實(shí)際上,多態(tài)、依賴注入、基于接口而非實(shí)現(xiàn)編程,以及抽象意識,說的都是同一種設(shè)計(jì)思路:提升代碼擴(kuò)展性,只是從不同的角度、不同的層面來闡述而已?;诮涌诙菍?shí)現(xiàn)編程的設(shè)計(jì)初衷是,將接口和實(shí)現(xiàn)相分離,封裝不穩(wěn)定的實(shí)現(xiàn),暴露穩(wěn)定的接口。要遵從該原則,需要做到下面這點(diǎn)。(1函數(shù)的命名不能暴露任何實(shí)現(xiàn)細(xì)節(jié)。比如,就不符合要求,改為更加抽象的命名方式:。(2封裝具體的實(shí)現(xiàn)細(xì)節(jié)。例如對上傳(或下載)流程進(jìn)行封裝,對外提供一個包裹所有上傳(或下載)細(xì)節(jié)的方法,給調(diào)用者使用。(3為實(shí)現(xiàn)類定義抽象的接口。使用者依賴接口,而不是具體的實(shí)現(xiàn)類來編程。如何

5、在項(xiàng)目中靈活運(yùn)用:(1對于一些比較確定的、短期內(nèi)可能就會擴(kuò)展,或者需求改動對代碼結(jié)構(gòu)影響比較大的情況,或者實(shí)現(xiàn)成本不高的擴(kuò)展點(diǎn),在編寫代碼的時候,就可以事先做些擴(kuò)展性設(shè)計(jì)。(2但對于一些不確定未來是否要支持的需求,或者實(shí)現(xiàn)起來比較復(fù)雜的擴(kuò)展點(diǎn),可以等到有需求驅(qū)動的時候,再通過重構(gòu)代碼的方式來支持?jǐn)U展的需求。里式替換原則(P是指子類對象能夠替換程序中父類對象出現(xiàn)的任何地方,并且保證原來程序的邏輯行為不變及正確性不被破壞。里式替換原則就是子類完美繼承父類的設(shè)計(jì)初衷,并做了增強(qiáng)。與多態(tài)的區(qū)別:多態(tài)是面向?qū)ο缶幊痰囊淮筇匦?,也是面向?qū)ο缶幊陶Z言的一種語法和代碼實(shí)現(xiàn)的思路。里式替換是一種設(shè)計(jì)原則,用來指

6、導(dǎo)繼承關(guān)系中子類該如何設(shè)計(jì)的。按照協(xié)議來設(shè)計(jì):子類在設(shè)計(jì)的時候,要遵守父類的行為約定(或者叫協(xié)議)。父類定義了函數(shù)的行為約定,那子類可以改變函數(shù)的內(nèi)部實(shí)現(xiàn)邏輯,但不能改變函數(shù)原有的行為約定。這里的行為約定包括:a、函數(shù)聲明要實(shí)現(xiàn)的功能;b、對輸入、輸出、異常的約定;c、甚至包括注釋中所羅列的任何特殊說明。實(shí)際上,定義中父類和子類之間的關(guān)系,也可以替換成接口和實(shí)現(xiàn)類之間的關(guān)系。ISP接口隔離原則(InterfaceSegregationPrinciple,ISP)是指接口的調(diào)用者或使用者不應(yīng)該強(qiáng)迫依賴它不需要的接口。接口可理解為下面三種東西:一組API接口集合。例如將刪除接口單獨(dú)放到另外一個接口

7、RestrictedUserService中,而不是UserService中,只打包提供給后臺管理系統(tǒng)來使用。單個API接口或函數(shù)。函數(shù)的設(shè)計(jì)要功能單一,不要將多個不同的功能邏輯在一個函數(shù)中實(shí)現(xiàn)。OOP中的接口概念。例如設(shè)計(jì)一個功能單一的接口:Updater。ScheduledUpdater只依賴Updater這個跟熱更新相關(guān)的接口,不需要被強(qiáng)迫去依賴不需要的Viewer接口。與單一職責(zé)原則的區(qū)別:單一職責(zé)原則針對的是模塊、類、接口的設(shè)計(jì)。接口隔離原則相對于單一職責(zé)原則,一方面它更側(cè)重于接口的設(shè)計(jì),另一方面它的思考角度不同。它提供了一種判斷接口是否職責(zé)單一的標(biāo)準(zhǔn):通過調(diào)用者如何使用接口來間接地

8、判定。如果調(diào)用者只使用部分接口或接口的部分功能,那接口的設(shè)計(jì)就不夠職責(zé)單一。DIP依賴倒置原則(DependencyInversionPrinciple,DIP)高層模塊不要依賴低層模塊。高層模塊和低層模塊應(yīng)該通過抽象來互相依賴。除此之外,抽象不要依賴具體實(shí)現(xiàn)細(xì)節(jié),具體實(shí)現(xiàn)細(xì)節(jié)依賴抽象。調(diào)用者屬于高層,被調(diào)用者屬于低層。在平時的業(yè)務(wù)代碼開發(fā)中,高層模塊依賴底層模塊是沒有任何問題的。這條原則主要還是用來指導(dǎo)框架層面的設(shè)計(jì)。控制反轉(zhuǎn)(InversionOfControl,IOC)“控制”指的是對程序執(zhí)行流程的控制,而“反轉(zhuǎn)”指的是在沒有使用框架之前,程序員自己控制整個程序的執(zhí)行。在使用框架之后,整

9、個程序的執(zhí)行流程可以通過框架來控制。流程的控制權(quán)從程序員“反轉(zhuǎn)”到了框架。框架提供了一個可擴(kuò)展的代碼骨架,用來組裝對象、管理整個執(zhí)行流程。程序員利用框架進(jìn)行開發(fā)的時候,只需要往預(yù)留的擴(kuò)展點(diǎn)上,添加跟自己業(yè)務(wù)相關(guān)的代碼,就可以利用框架來驅(qū)動整個程序流程的執(zhí)行??刂品崔D(zhuǎn)并不是一種具體的實(shí)現(xiàn)技巧,而是一個比較籠統(tǒng)的設(shè)計(jì)思想,一般用來指導(dǎo)框架層面的設(shè)計(jì)。依賴注入(DependencyInjection,DI)不通過new()的方式在類內(nèi)部創(chuàng)建依賴類對象,而是將依賴的類對象在外部創(chuàng)建好之后,通過構(gòu)造函數(shù)、函數(shù)參數(shù)等方式傳遞(或注入)給類使用。KISS和丫AGNIKISS原則的英文描述有好幾個版本。Kee

10、pItSimpleandStupid.KeepItShortandSimple.KeepItSimpleandStraightforward.它們要表達(dá)的意思其實(shí)差不多,翻譯成中文就是:盡量保持簡單。代碼足夠簡單,也就意味著很容易讀懂,bug比較難隱藏。即便出現(xiàn)bug,修復(fù)起來也比較簡單。指導(dǎo)如何開發(fā)出KISS原則的方法論:代碼行數(shù)越少并不是就越“簡單”。代碼邏輯復(fù)雜不違背KISS原則。如何寫出滿足KISS原則:不要使用同事可能不懂的技術(shù)來實(shí)現(xiàn)代碼。例如例子中的正則表達(dá)式,還有一些編程語言中過于高級的語法等。_(2)不要重復(fù)造輪子,要善于使用已經(jīng)有的工具類庫。經(jīng)驗(yàn)證明,自己去實(shí)現(xiàn)這些類庫,出b

11、ug的概率會更高,維護(hù)的成本也比較高。不要過度優(yōu)化。不要過度使用一些奇技淫巧(比如,位運(yùn)算代替算術(shù)運(yùn)算、復(fù)雜的條件語句代替if-else、使用一些過于底層的函數(shù)等)來優(yōu)化代碼,犧牲代碼的可讀性。注意,在做開發(fā)的時候,一定不要過度設(shè)計(jì),不要覺得簡單的東西就沒有技術(shù)含量。實(shí)際上,越是能用簡單的方法解決復(fù)雜的問題,越能體現(xiàn)一個人的能力。YAGNI(YouAintGonnaNeedIt)是指不要去設(shè)計(jì)當(dāng)前用不到的功能;不要去編寫當(dāng)前用不到的代碼。其核心思想就是:不要做過度設(shè)計(jì)。例如不要在項(xiàng)目中提前引入不需要依賴的開發(fā)包,在未用到ZooKeeper之前沒必要提前編寫這部分代碼。KISS原則講的是“如何做

12、的問題(盡量保持簡單),而YAGNI原則說的是“要不要做的問題(當(dāng)前不需要的就不要做)。DRYDRY(DontRepeatYourself)的定義非常簡單,三種典型的代碼重復(fù)情況:(1)實(shí)現(xiàn)邏輯重復(fù)將isValidUserName()和isValidPassword()兩個函數(shù)中的重復(fù)代碼合并到isValidUserNameOrPassword()函數(shù),負(fù)責(zé)兩件事情,違反了“單一職責(zé)原則”和“接口隔離原則”。雖然從代碼實(shí)現(xiàn)邏輯上看起來它們是重復(fù)的,但是從語義上并不重復(fù)。所謂“語義不重復(fù)”指的是:從功能上來看,這兩個函數(shù)干的是完全不重復(fù)的兩件事情,一個是校驗(yàn)用戶名,另一個是校驗(yàn)密碼。盡管在目前的

13、設(shè)計(jì)中,兩個校驗(yàn)邏輯是完全一樣的,但如果按照第二種寫法,將兩個函數(shù)的合并,那就會存在潛在的問題。功能語義重復(fù)在同一個項(xiàng)目代碼中有兩個函數(shù):isValidIp()和checklflpValid()。盡管兩個函數(shù)的命名不同,實(shí)現(xiàn)邏輯不同,但功能是相同的,都是用來判定IP地址是否合法的。在這個例子中,盡管兩段代碼的實(shí)現(xiàn)邏輯不重復(fù),但語義重復(fù),也就是功能重復(fù),可以認(rèn)為它違反了DRY原則。代碼執(zhí)行重復(fù)在這個例子中,既沒有邏輯重復(fù),也沒有語義重復(fù),但仍然違反了DRY原則。這是因?yàn)榇a中存在“執(zhí)行重復(fù)”。在login()函數(shù)中,email的校驗(yàn)邏輯被執(zhí)行了兩次。一次是在調(diào)用checkIfUserExiste

14、d()函數(shù)的時候,另一次是調(diào)用getUserByEmail()函數(shù)的時候。除此之外,代碼中還有一處比較隱蔽的執(zhí)行重復(fù),login()函數(shù)并不需要調(diào)用checkIfUserExisted()函數(shù),只需要調(diào)用一次getUserByEmail()函數(shù),從數(shù)據(jù)庫中獲取到用戶的email、password等信息,然后跟用戶輸入的email、password信息做對比,依次判斷是否登錄成功。三個概念:代碼復(fù)用(CodeResue)表示一種行為:在開發(fā)新功能的時候,盡量復(fù)用已經(jīng)存在的代碼。代碼復(fù)用性(CodeReusability)表示一段代碼可被復(fù)用的特性或能力:在編寫代碼的時候,讓代碼盡量可復(fù)用。DRY

15、原則是一條原則:不要寫重復(fù)的代碼。區(qū)分:首先,“不重復(fù)”并不代表“可復(fù)用”。在一個項(xiàng)目代碼中,可能不存在任何重復(fù)的代碼,但也并不表示里面有可復(fù)用的代碼,不重復(fù)和可復(fù)用完全是兩個概念。其次,“復(fù)用”和“可復(fù)用性咲注角度不同。代碼“可復(fù)用性”是從代碼開發(fā)者的角度來講的,“復(fù)用”是從代碼使用者的角度來講的。比如,A同事編寫了一個UrlUtils類,代碼的“可復(fù)用性”很好。B同事在開發(fā)新功能的時候,直接復(fù)用”A同事編寫的UrlUtils類。盡管復(fù)用、可復(fù)用性、DRY原則這三者從理解上有所區(qū)別,但實(shí)際上要達(dá)到的目的都是類似的,都是為了減少代碼量,提高代碼的可讀性、可維護(hù)性?!皬?fù)用”這個概念不僅可以指導(dǎo)細(xì)

16、粒度的模塊、類、函數(shù)的設(shè)計(jì)開發(fā),實(shí)際上,一些框架、類庫、組件等的產(chǎn)生也都是為了達(dá)到復(fù)用的目的。比如,Spring框架、GoogleGuava類庫、UI組件等等。提高代碼復(fù)用性7個方法:(1)減少代碼耦合。對于高度耦合的代碼,當(dāng)希望復(fù)用其中的一個功能,想把這個功能的代碼抽取出來成為一個獨(dú)立的模塊、類或者函數(shù)的時候,往往會發(fā)現(xiàn)牽一發(fā)而動全身。滿足單一職責(zé)原則。越細(xì)粒度的代碼,代碼的通用性會越好,越容易被復(fù)用。模塊化??蓪⒛K理解為單個類、函數(shù)。獨(dú)立的模塊就像一塊一塊的積木,更加容易復(fù)用,可以直接拿來搭建更加復(fù)雜的系統(tǒng)。業(yè)務(wù)與非業(yè)務(wù)邏輯分離。越是跟業(yè)務(wù)無關(guān)的代碼越是容易復(fù)用,越是針對特定業(yè)務(wù)的代碼越

17、難復(fù)用。通用代碼下沉。從分層的角度來看,越底層的代碼越通用、會被越多的模塊調(diào)用,越應(yīng)該設(shè)計(jì)得足夠可復(fù)用。為了避免交叉調(diào)用導(dǎo)致調(diào)用關(guān)系混亂,只允許上層代碼調(diào)用下層代碼及同層代碼之間的調(diào)用,杜絕下層代碼調(diào)用上層代碼。繼承、多態(tài)、抽象、封裝。利用繼承,可以將公共的代碼抽取到父類,子類復(fù)用父類的屬性和方法。利用多態(tài),可以動態(tài)地替換一段代碼的部分邏輯,讓這段代碼可復(fù)用。越抽象、越不依賴具體的實(shí)現(xiàn),越容易復(fù)用。代碼封裝成模塊,隱藏可變的細(xì)節(jié)、暴露不變的接口,就越容易復(fù)用。應(yīng)用模板等設(shè)計(jì)模式。模板模式利用了多態(tài)來實(shí)現(xiàn),可以靈活地替換其中的部分代碼,整個流程模板代碼可復(fù)用。實(shí)際上,除非有非常明確的復(fù)用需求,否

18、則,為了暫時用不到的復(fù)用需求,花費(fèi)太多的時間、精力,投入太多的開發(fā)成本,并不是一個值得推薦的做法。這也違反之前講到的YAGNI原則。除此之外,有一個著名的原則,叫作RuleofThree”。第一次編寫代碼的時候,不考慮復(fù)用性;第二次遇到復(fù)用場景的時候,再進(jìn)行重構(gòu)使其復(fù)用。LOD迪米特法則(LawofDemeter,LOD)也叫最小知識原則(TheLeastKnowledgePrinciple),是指每個模塊只應(yīng)該了解那些與它關(guān)系密切的模塊的有限知識?;蛘哒f,每個模塊只和自己的朋友說話”,不和陌生人“說話”。換句話說,就是不該有直接依賴關(guān)系的類之間,不要有依賴;有依賴關(guān)系的類之間,盡量只依賴必要

19、的接口(也就是定義中的“有限知識”)。迪米特法則是希望減少類之間的耦合,讓類越獨(dú)立越好。每個類都應(yīng)該少了解系統(tǒng)的其他部分。一旦發(fā)生變化,需要了解這一變化的類就會比較少。單一職責(zé)原則、接口隔離原則、基于接口而非實(shí)現(xiàn)編程和迪米特法則,目的都是實(shí)現(xiàn)高內(nèi)聚低耦合,但是出發(fā)的角度不一樣,單一職責(zé)是從自身提供的功能出發(fā),迪米特法則是從關(guān)系出發(fā),針對接口而非實(shí)現(xiàn)編程是使用者的角度,殊途同歸。高內(nèi)聚,就是指相近的功能應(yīng)該放到同一個類中,不相近的功能不要放到同一個類中。放到同一個類中,修改會比較集中,代碼容易維護(hù)。松耦合,在代碼中,類與類之間的依賴關(guān)系簡單清晰。即使兩個類有依賴關(guān)系,一個類的代碼改動不會或者很少

20、導(dǎo)致依賴類的代碼改動。積分系統(tǒng)合理地將功能劃分到不同模塊。為了避免業(yè)務(wù)知識的耦合,讓下層系統(tǒng)更加通用,一般來講,不希望下層系統(tǒng)(也就是被調(diào)用的系統(tǒng))包含太多上層系統(tǒng)(也就是調(diào)用系統(tǒng))的業(yè)務(wù)信息,但是,可以接受上層系統(tǒng)包含下層系統(tǒng)的業(yè)務(wù)信息。比如,訂單系統(tǒng)、優(yōu)惠券系統(tǒng)、換購商城等作為調(diào)用積分系統(tǒng)的上層系統(tǒng),可以包含一些積分相關(guān)的業(yè)務(wù)信息。但是,反過來,積分系統(tǒng)中最好不要包含太多跟訂單、優(yōu)惠券、換購等相關(guān)的信息。設(shè)計(jì)模塊與模塊之間的交互關(guān)系。交互方式有兩種,一種是同步接口調(diào)用,另一種是利用消息中間件異步調(diào)用。第一種方式簡單直接,第二種方式的解耦效果更好。比如,用戶下訂單成功之后,訂單系統(tǒng)推送一條消

21、息到消息中間件,營銷系統(tǒng)訂閱訂單成功消息,觸發(fā)執(zhí)行相應(yīng)的積分兌換邏輯。這樣訂單系統(tǒng)就跟營銷系統(tǒng)完全解耦,訂單系統(tǒng)不需要知道任何跟積分相關(guān)的邏輯,而營銷系統(tǒng)也不需要直接跟訂單系統(tǒng)交互。設(shè)計(jì)模塊的接口、數(shù)據(jù)庫、業(yè)務(wù)模型。數(shù)據(jù)庫和接口的設(shè)計(jì)非常重要,一旦設(shè)計(jì)好并投入使用之后,這兩部分都不能輕易改動。改動數(shù)據(jù)庫表結(jié)構(gòu),需要涉及數(shù)據(jù)的遷移和適配;改動接口,需要推動接口的使用者作相應(yīng)的代碼修改。a、數(shù)據(jù)庫的設(shè)計(jì)比較簡單。實(shí)際上,只需要一張記錄積分流水明細(xì)的表就可以了。b、為了兼顧易用性和性能,可以借鑒facade(外觀)設(shè)計(jì)模式,在職責(zé)單一的細(xì)粒度接口之上,再封裝一層粗粒度的接口給外部使用。c、將它跟營銷

22、系統(tǒng)放到一個項(xiàng)目中開發(fā)部署,只要做好代碼的模塊化和解耦即可。為什么要分MVC三層開發(fā)?(1)分層能起到代碼復(fù)用的作用。同一個Repository可能會被多個Service來調(diào)用,同一個Service可能會被多個Controller調(diào)用。分層能起到隔離變化的作用?;诮涌诙菍?shí)現(xiàn)編程的設(shè)計(jì)思想,Service層使用Repository層提供的接口,并不關(guān)心其底層依賴的是哪種具體的數(shù)據(jù)庫。當(dāng)需要替換數(shù)據(jù)庫的時候,只需要改動Repository層的代碼。分層能起到隔離關(guān)注點(diǎn)的作用。Repository層只關(guān)注數(shù)據(jù)的讀寫。Service層只關(guān)注業(yè)務(wù)邏輯,不關(guān)注數(shù)據(jù)的來源。Controller層只關(guān)注與

23、外界打交道,數(shù)據(jù)校驗(yàn)、封裝、格式轉(zhuǎn)換,并不關(guān)心業(yè)務(wù)邏輯。分層能提高代碼的可測試性。Repsitory層的代碼通過依賴注入的方式供Service層使用,當(dāng)要測試包含核心業(yè)務(wù)邏輯的Service層代碼的時候,可以用mock的數(shù)據(jù)源替代真實(shí)的數(shù)據(jù)庫,注入到Service層代碼中。分層能應(yīng)對系統(tǒng)的復(fù)雜性。拆分有垂直和水平兩個方向。水平方向基于業(yè)務(wù)來做拆分,就是模塊化;垂直方向基于流程來做拆分,就是這里說的分層。統(tǒng)計(jì)系統(tǒng)對于這樣一個通用的框架的開發(fā),還需要考慮很多非功能性的需求。易用性,框架是否易集成、易插拔、跟業(yè)務(wù)代碼是否松耦合、提供的接口是否夠靈活等等。性能,一方面,希望它低延遲,即統(tǒng)計(jì)代碼不影響或

24、很少影響接口本身的響應(yīng)時間;另一方面,希望框架本身對內(nèi)存的消耗不能太人。擴(kuò)展性,從框架使用者的角度來說,可以在不修改框架源碼的情況下,為框架擴(kuò)展新的功能,類似給框架開發(fā)插件。容錯性,不能因?yàn)榭蚣鼙旧淼漠惓?dǎo)致接口請求出錯。通用性,除了接口統(tǒng)計(jì)這樣一個需求,還可以適用到其他哪些場景中,比如SQL請求時間的統(tǒng)計(jì)信息、業(yè)務(wù)統(tǒng)計(jì)信息(比如支付成功率)等??蚣茉O(shè)計(jì):對于稍微復(fù)雜系統(tǒng)的開發(fā),很多人覺得不知從何開始。可以借鑒TDD(測試驅(qū)動開發(fā))和Prototype(最小原型)的思想,先聚焦于一個簡單的應(yīng)用場景,基于此設(shè)計(jì)實(shí)現(xiàn)一個簡單的原型。把整個框架分為四個模塊:數(shù)據(jù)采集、存儲、聚合統(tǒng)計(jì)、顯示。接下來,就

25、按照之前講的面向?qū)ο笤O(shè)計(jì)的幾個步驟,來重新劃分、設(shè)計(jì)類。劃分職責(zé)進(jìn)而識別出有哪些類。MetricsCollector類、Metricsstorage接口、Aggregator類、ConsoleReporter類和EmailReporter類。定義類及類與類之間的關(guān)系。先在IDE中創(chuàng)建好這幾個類,然后開始試著定義它們的屬性和方法。在設(shè)計(jì)類、類與類之間交互的時候,不斷地用之前學(xué)過的設(shè)計(jì)原則和思想來審視設(shè)計(jì)是否合理。比如,是否滿足單一職責(zé)原則、開閉原則、依賴注入、KISS原則、DRY原則、迪米特法則,是否符合基于接口而非實(shí)現(xiàn)編程思想,代碼是否高內(nèi)聚、低耦合,是否可以抽象出可復(fù)用代碼等等。將類組裝起來

26、并提供執(zhí)行入口。有兩個執(zhí)行入口:一個是MetricsCollector類,提供了一組API來采集原始數(shù)據(jù);另一個是ConsoleReporter類和EmailReporter類,用來觸發(fā)統(tǒng)計(jì)顯示。二、規(guī)范與重構(gòu)重構(gòu)重構(gòu)目的(why)重構(gòu)是一種對軟件內(nèi)部結(jié)構(gòu)的改善,目的是在不改變軟件可見行為的情況下,使其更易理解,修改成本更低。即在保持功能不變的前提下,利用設(shè)計(jì)思想、原則、模式、編程規(guī)范等理論來優(yōu)化代碼,修改設(shè)計(jì)上的不足,提高代碼質(zhì)量。重構(gòu)對象(what)可以籠統(tǒng)地分為大規(guī)模高層次重構(gòu)(以下簡稱為大型重構(gòu)”)和小規(guī)模低層次的重構(gòu)(以下簡稱為小型重構(gòu)”)。大型重構(gòu)指的是對頂層代碼設(shè)計(jì)的重構(gòu),包括:

27、系統(tǒng)、模塊、代碼結(jié)構(gòu)、類與類之間的關(guān)系等的重構(gòu),重構(gòu)的手段有:分層、模塊化、解耦、抽象可復(fù)用組件等等。這類重構(gòu)的工具就是學(xué)習(xí)過的那些設(shè)計(jì)思想、原則和模式。小型重構(gòu)指的是對代碼細(xì)節(jié)的重構(gòu),主要是針對類、函數(shù)、變量等代碼級別的重構(gòu),比如規(guī)范命名、規(guī)范注釋、消除超大類或函數(shù)、提取重復(fù)代碼等等。小型重構(gòu)更多的是利用后面要講到的編碼規(guī)范。重構(gòu)時機(jī)(when)提倡的重構(gòu)策略是持續(xù)重構(gòu)。平時沒有事情的時候,可以看看項(xiàng)目中有哪些寫得不夠好的、可以優(yōu)化的代碼,主動去重構(gòu)一下?;蛘咴谛薷摹⑻砑幽硞€功能代碼的時候,也可以順手把不符合編碼規(guī)范、不好的設(shè)計(jì)重構(gòu)一下。重構(gòu)方法(how)在進(jìn)行大型重構(gòu)的時候,要提前做好完善

28、的重構(gòu)計(jì)劃,有條不紊地分階段來進(jìn)行。每個階段完成一小部分代碼的重構(gòu),然后提交、測試、運(yùn)行,發(fā)現(xiàn)沒有問題之后,再繼續(xù)進(jìn)行下一階段的重構(gòu),保證代碼倉庫中的代碼一直處于可運(yùn)行、邏輯正確的狀態(tài)。每個階段,都要控制好重構(gòu)影響到的代碼范圍,考慮好如何兼容老的代碼邏輯,必要的時候還需要寫一些兼容過渡代碼。除了人工去發(fā)現(xiàn)低層次的質(zhì)量問題,還可以借助很多成熟的靜態(tài)代碼分析工具(比如CheckStyle、FindBugs、PMD),來自動發(fā)現(xiàn)代碼中的問題,然后針對性地進(jìn)行重構(gòu)優(yōu)化。單元測試單元測試(UnitTesting)由研發(fā)工程師自己來編寫,用來測試自己寫的代碼的正確性。單元測試的好處:單元測試能有效地幫你發(fā)

29、現(xiàn)代碼中的bug。寫單元測試能幫你發(fā)現(xiàn)代碼設(shè)計(jì)上的問題。比如沒有使用依賴注入、大量使用靜態(tài)函數(shù)、全局變量、代碼高度耦合等。單元測試是對集成測試的有力補(bǔ)充。大部分異常情況都比較難在測試環(huán)境中模擬,比如除數(shù)未判空、網(wǎng)絡(luò)超時。寫單元測試的過程本身就是代碼重構(gòu)的過程。相當(dāng)于對代碼的一次自我CodeReview。閱讀單元測試能幫助你快速熟悉代碼。單元測試是測試驅(qū)動開發(fā)(Test-DrivenDevelopment,TDD)可落地執(zhí)行的改進(jìn)方案。編寫單元測試的經(jīng)驗(yàn)總結(jié)包括以下幾點(diǎn):盡管單元測試的代碼量可能是被測代碼本身的12倍,寫的過程很繁瑣,但并不是很耗時。對單元測試代碼的質(zhì)量可以放低一些要求。命名稍微

30、有些不規(guī)范,代碼稍微有些重復(fù),也都是沒有問題的。不管覆蓋率的計(jì)算方式如何高級,將覆蓋率作為衡量單元測試質(zhì)量的唯一標(biāo)準(zhǔn)是不合理的。單元測試不要依賴被測試函數(shù)的具體實(shí)現(xiàn)邏輯,它只關(guān)心被測函數(shù)實(shí)現(xiàn)了什么功能。團(tuán)隊(duì)內(nèi)部需要統(tǒng)一單元測試框架。單元測試為何難落地執(zhí)行?方面,寫單元測試本身比較繁瑣,技術(shù)挑戰(zhàn)不大,很多程序員不愿意去寫;另一方面,國內(nèi)研發(fā)比較偏向快、糙、猛”,容易因?yàn)殚_發(fā)進(jìn)度緊,導(dǎo)致單元測試的執(zhí)行虎頭蛇尾。最后,關(guān)鍵問題還是團(tuán)隊(duì)沒有建立對單元測試正確的認(rèn)識,覺得可有可無,單靠督促很難執(zhí)行得很好??蓽y試性所謂代碼的可測試性,就是針對代碼編寫單元測試的難易程度。對于一段代碼,如果很難為其編寫單元測

31、試,或者單元測試寫起來很費(fèi)勁,需要依靠單元測試框架中很高級的特性,那往往就意味著代碼設(shè)計(jì)得不夠合理,代碼的可測試性不好。依賴注入是編寫可測試性代碼的最有效手段。通過依賴注入,在編寫單元測試的時候,可以通過mock的方法依賴外部服務(wù)。注意,只往里寫入數(shù)據(jù),并不讀取數(shù)據(jù),不參與業(yè)務(wù)邏輯的執(zhí)行,不會影響代碼邏輯的正確性,這些對象沒有必要mock。除此之外,些只是為了存儲數(shù)據(jù)的值對象,比如String、Map、UseVo,也沒必要通過依賴注入的方式來創(chuàng)建,直接在類中通過new創(chuàng)建就可以了。常見的測試不友好的代碼有下面這5種:代碼中包含未決行為邏輯,即代碼的輸出是隨機(jī)或者說不確定的,比如,跟時間、隨機(jī)數(shù)

32、有關(guān)的代碼。濫用可變?nèi)肿兞?。濫用靜態(tài)方法,因?yàn)殪o態(tài)方法很難mock。使用復(fù)雜的繼承關(guān)系,如果父類需要mock某個依賴對象才能進(jìn)行單元測試,那么底層子類要一個一個mock很多依賴對象。高度耦合的代碼,在編寫單元測試的時候,可能需要mock這十幾個依賴的對象。解耦代碼是否需要解耦?間接的衡量標(biāo)準(zhǔn)有很多,比如,看修改代碼會不會牽一發(fā)而動全身。還有一個直接的衡量標(biāo)準(zhǔn),那就是把模塊與模塊之間、類與類之間的依賴關(guān)系畫出來,根據(jù)依賴關(guān)系圖的復(fù)雜性來判斷是否需要解耦重構(gòu)。如何進(jìn)行解耦:封裝與抽象,有效地隱藏實(shí)現(xiàn)的復(fù)雜性,隔離實(shí)現(xiàn)的易變性,給依賴的模塊提供穩(wěn)定且易用的抽象接口。中間層,簡化模塊或類之間的依賴關(guān)

33、系,并能起到過渡的作用,讓開發(fā)和重構(gòu)同步進(jìn)行,不互相干擾。a、第一階段:引入一個中間層,包裹老的接口,提供新的接口定義。b、第二階段:新開發(fā)的代碼依賴中間層提供的新接口。c、第三階段:將依賴?yán)辖涌诘拇a改為調(diào)用新接口。d、第四階段:確保所有的代碼都調(diào)用新接口之后,刪除掉老的接口。模塊化,將系統(tǒng)劃分成各個獨(dú)立的模塊,讓不同的人負(fù)責(zé)不同的模塊,這樣即便在不了解全部細(xì)節(jié)的情況下,管理者也能協(xié)調(diào)各個模塊,讓整個系統(tǒng)有效運(yùn)轉(zhuǎn)。其他設(shè)計(jì)思想和原則,單一職責(zé)原則,基于接口而非實(shí)現(xiàn)編程,依賴注入,多用組合少用繼承,迪米特法則。編碼規(guī)范命名以能準(zhǔn)確達(dá)意為目標(biāo),利用上下文簡化命名,命名要可讀、可搜索。對于接口的命

34、名,一種是加前綴T,另一種是加后綴“Impl,;對于抽象類的命名,一種是帶上前綴Abstract,另一種是不帶前綴。注釋注釋的內(nèi)容主要包含這樣四個方面:做什么、為什么、怎么做、怎么用。對一些邊界條件、特殊情況進(jìn)行說明,以及對函數(shù)輸入、輸出、異常進(jìn)行說明。寫一些總結(jié)性的說明、特殊情況的說明。對于邏輯比較復(fù)雜的代碼或者比較長的函數(shù),借助總結(jié)性的注釋來讓代碼結(jié)構(gòu)更清晰、更有條理。類和函數(shù)一定要寫注釋,而且要寫得盡可能全面、詳細(xì),而函數(shù)內(nèi)部的注釋要相對少一些,一般都是靠好的命名、提煉函數(shù)、解釋性變量、總結(jié)性注釋來提高代碼的可讀性。類、函數(shù)規(guī)模當(dāng)一個類的代碼讀起來讓你感覺頭大了,實(shí)現(xiàn)某個功能時不知道該用

35、哪個函數(shù)了,想用哪個函數(shù)翻半天都找不到了,只用到一個小功能要引入整個類(類中包含很多無關(guān)此功能實(shí)現(xiàn)的函數(shù))的時候,這就說明類的行數(shù)過多了。對于函數(shù)代碼行數(shù)的最大限制,網(wǎng)上有一種說法,那就是不要超過一個顯示屏的垂直高度。一行代碼的長度行代碼最長不能超過IDE顯示的寬度。需要滾動鼠標(biāo)才能查看一行的全部代碼,顯然不利于代碼的閱讀。善用空行分割單元塊在類的成員變量與函數(shù)之間、靜態(tài)成員變量與普通成員變量之間、各函數(shù)之間、甚至各成員變量之間,都可以通過添加空行的方式,讓這些不同模塊的代碼之間,界限更加明確??s進(jìn)Java語言傾向于兩格縮進(jìn),PHP語言傾向于四格縮進(jìn)。大括號是否要另起一行PHP程序員喜歡另起一

36、行,Java程序員喜歡跟上一條語句放到一起。類中成員的排列順序在Java類文件中,先要書寫類所屬的包名,然后再羅列import引入的依賴類。在Google編碼規(guī)范中,依賴類按照字母順序從小到大排列。在類中,成員變量排在函數(shù)的前面。成員變量之間或函數(shù)之間,都是按照先靜態(tài)(靜態(tài)函數(shù)或靜態(tài)成員變量)、后普通(非靜態(tài)函數(shù)或非靜態(tài)成員變量)”的方式來排列的。成員變量之間或函數(shù)之間,還會按照作用域范圍從大到小的順序來排列,先寫public成員變量或函數(shù),然后是protected的,最后是private的。把代碼分割成更小的單元塊要有模塊化和抽象思維,善于將大塊的復(fù)雜邏輯提煉成類或者函數(shù),屏蔽掉細(xì)節(jié),讓閱讀代碼的人不至于迷失在細(xì)節(jié)中。避免函數(shù)參數(shù)過多函數(shù)包含3、4個參數(shù)的時候還是能接受的。針對參數(shù)過多的情況,一般有2種處理方法。a、考慮函數(shù)是否職責(zé)單一,是否能通過拆分成多個函數(shù)的方式來減少參數(shù)。b、將函數(shù)的參數(shù)封裝成對象。勿用函數(shù)參數(shù)來控制邏輯不要在函數(shù)中使用布爾類型的標(biāo)識參數(shù)來控制內(nèi)部邏輯,true的時候走這塊邏輯,false的時候走另一塊邏輯。這明顯違背了單一職責(zé)原則和接口隔離原則,建議將其拆成兩個函數(shù)。函數(shù)設(shè)計(jì)要職責(zé)單一相對于類和模塊,函數(shù)的粒度比較小,代碼行數(shù)少,所以在應(yīng)用單一職責(zé)原則的時候,沒有像應(yīng)用到類

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論