clean code代碼整潔

clean code,顧名思義就是整潔的代碼拴签,或者說(shuō)清晰孝常、漂亮的代碼,相信大多數(shù)工程師都希望自己能寫出這樣的代碼蚓哩。

也許這是個(gè)千人千面的話題构灸,每個(gè)工程師都有自己的理解。比如我岸梨,從一個(gè)天天被罵代碼寫得爛的人喜颁,逐漸學(xué)習(xí)成長(zhǎng),到現(xiàn)在也能寫的出“人模人樣”的代碼來(lái)了曹阔。這期間算是積累了一點(diǎn)經(jīng)驗(yàn)心得半开,想和大家分享,拋磚引玉赃份。

本文主要針對(duì)面向?qū)ο缶幊痰腸lean code來(lái)闡述寂拆,面向過(guò)程代碼的思路會(huì)比較不同,不在本文的討論范疇抓韩。

代碼整潔的大前提
代碼大部分時(shí)候是用來(lái)維護(hù)的纠永,而不是用來(lái)實(shí)現(xiàn)功能的
這個(gè)原則適用于大部分的工程。我們的代碼园蝠,一方面是編譯好讓機(jī)器執(zhí)行渺蒿,完成功能需求痢士;另一方面彪薛,是寫給身邊的隊(duì)友和自己看的茂装,需要長(zhǎng)期維護(hù),而且大部分項(xiàng)目都不是朝生夕死的短命鬼善延。

大部分情況下少态,如果不能寫出清晰好看的代碼,可能自己一時(shí)爽快易遣,后續(xù)維護(hù)付出的代價(jià)和成本將遠(yuǎn)高于你的想象彼妻。

對(duì)清晰好看代碼的追求精神,比所有的技巧都要重要豆茫。

優(yōu)秀的代碼大部分是可以自描述的侨歉,好于文檔和注釋
當(dāng)你翻看很多開源代碼時(shí),會(huì)發(fā)現(xiàn)注釋甚至比我們自己寫的項(xiàng)目都少揩魂,但是卻能看的很舒服幽邓。當(dāng)讀完源碼時(shí),很多功能設(shè)計(jì)就都清晰明了了火脉。通過(guò)仔細(xì)斟酌的方法命名牵舵、清晰的流程控制,代碼本身就可以拿出來(lái)當(dāng)作文檔使用倦挂,而且它永遠(yuǎn)不會(huì)過(guò)期畸颅。

相反,注釋不能讓寫的爛的代碼變的更好方援。如果別人只能依靠注釋讀懂你的代碼的時(shí)候荆秦,你一定要反思代碼出現(xiàn)了什么問(wèn)題(當(dāng)然,這里不是說(shuō)大家不要寫注釋了)苦丁。

說(shuō)下比較適合寫注釋的兩種場(chǎng)景:

public interface碟案,向別人明確發(fā)布你功能的語(yǔ)義,輸入輸出笛丙,且不需要關(guān)注實(shí)現(xiàn)漾脂。
功能容易有歧義的點(diǎn),或者涉及比較深層專業(yè)知識(shí)的時(shí)候胚鸯。比如骨稿,如果你寫一個(gè)客戶端,各種config參數(shù)的含義等姜钳。
設(shè)計(jì)模式只是手段坦冠,代碼清晰才是目的
之前見過(guò)一些所謂“高手”的代碼都比較抽象,各種工廠哥桥、各種繼承辙浑。想找到一個(gè)實(shí)現(xiàn)總是要山路十八彎,一個(gè)工程里大部分的類是抽象類或者接口拟糕,找不到一兩句實(shí)現(xiàn)的代碼判呕,整個(gè)讀起代碼來(lái)很不順暢倦踢。我跟他聊起來(lái)的時(shí)候,他的主要立場(chǎng)是:保留合適的擴(kuò)展點(diǎn)侠草,克服掉所有的硬編碼辱挥。

其實(shí)在我看來(lái),也許他的代碼被“過(guò)度設(shè)計(jì)”了边涕。首先必須要承認(rèn)的是晤碘,在同一個(gè)公司工作的同事,水平是參差不齊的功蜓。無(wú)論你用了如何高大上的設(shè)計(jì)园爷,如果大多數(shù)人都不能理解你的代碼或者讀起來(lái)很費(fèi)勁的話,其實(shí)這是一個(gè)失敗的設(shè)計(jì)式撼。

當(dāng)你的系統(tǒng)內(nèi)大部分抽象只有一個(gè)實(shí)現(xiàn)的時(shí)候腮介,要好好思考一下,是不是設(shè)計(jì)有點(diǎn)過(guò)度了端衰,清晰永遠(yuǎn)是第一準(zhǔn)則叠洗。

代碼整潔的常見手段
記住原則后,我們開始進(jìn)入實(shí)踐環(huán)節(jié)旅东,先來(lái)看下有哪些促成clean code的常見手段灭抑。

code review
很多大公司會(huì)用git的pull request機(jī)制來(lái)做code review。我們重點(diǎn)應(yīng)該review什么抵代?是代碼的格式腾节、業(yè)務(wù)邏輯還是代碼風(fēng)格?我想說(shuō)的是荤牍,凡是能通過(guò)機(jī)器檢查出來(lái)的事情案腺,無(wú)需通過(guò)人。比如換行康吵、注釋劈榨、方法長(zhǎng)度、代碼重復(fù)等晦嵌。除了基本功能需求的邏輯合理沒(méi)有bug外同辣,我們更應(yīng)該關(guān)注代碼的設(shè)計(jì)與風(fēng)格。比如惭载,一段功能是不是應(yīng)該屬于一個(gè)類旱函、是不是有很多相似的功能可以抽取出來(lái)復(fù)用、代碼太過(guò)冗長(zhǎng)難懂等等描滔。

我個(gè)人非常推崇集體code review棒妨,因?yàn)楹芏鄷r(shí)候,組里相對(duì)高級(jí)的工程師能夠一眼發(fā)現(xiàn)代碼存在較大設(shè)計(jì)缺陷含长,提出改進(jìn)意見或者重構(gòu)方式券腔。我們可以在整個(gè)小組內(nèi)形成一個(gè)好的文化傳承和風(fēng)格統(tǒng)一灾炭,并且很大程度上培養(yǎng)了大家對(duì)clean code的熱情。

勤于重構(gòu)
好的代碼颅眶,一般都不是一撮而就的。即使一開始設(shè)計(jì)的代碼非常優(yōu)秀田弥,隨著業(yè)務(wù)的快速迭代涛酗,也可能被改的面目全非。

為了避免重構(gòu)帶來(lái)的負(fù)面影響(delay需求或者帶來(lái)bug)偷厦,我們需要做好以下的功課:
① 掌握一些常見的“無(wú)痛”重構(gòu)技巧商叹,這在下文會(huì)有具體講解。
② 小步快跑只泼,不要企圖一口吃成個(gè)胖子剖笙。改一點(diǎn),測(cè)試一點(diǎn)请唱,一方面減少代碼merge的痛苦弥咪,另一方面減少上線的風(fēng)險(xiǎn)。
③ 建立自動(dòng)化測(cè)試機(jī)制十绑,要做到即使代碼改壞了聚至,也能保證系統(tǒng)最小核心功能的可用,并且保證自己修改的部分被測(cè)試覆蓋到本橙。
④ 熟練掌握IDE的自動(dòng)重構(gòu)功能扳躬。這些會(huì)很大程度上減少我們的體力勞動(dòng),避免犯錯(cuò)甚亭。

靜態(tài)檢查
現(xiàn)在市面上有很多代碼靜態(tài)檢查的工具贷币,也是發(fā)現(xiàn)bug和風(fēng)格不好的比較容易的方式】髡可以與發(fā)布系統(tǒng)做集成役纹,強(qiáng)制把主要問(wèn)題修復(fù)掉才可以上線。目前美團(tuán)點(diǎn)評(píng)技術(shù)團(tuán)隊(duì)內(nèi)部的研發(fā)流程中已經(jīng)普遍接入了Sonar質(zhì)量管理平臺(tái)暇唾。

多讀開源代碼和身邊優(yōu)秀同學(xué)的代碼
感謝開源社區(qū)字管,為我們提供了這么好的學(xué)習(xí)機(jī)會(huì)。無(wú)論是JDK的源碼信不,還是經(jīng)典的Netty嘲叔、Spring、Jetty抽活,還是一些小工具如Guava等硫戈,都是clean code的典范。多多學(xué)習(xí)下硕,多多反思和總結(jié)丁逝,必有收益汁胆。

代碼整潔的常見技巧
前面的內(nèi)容都屬于熱身,讓大家有個(gè)整體宏觀的認(rèn)識(shí)霜幼。下面終于進(jìn)入干貨環(huán)節(jié)了嫩码,我會(huì)分幾個(gè)角度講解編寫整潔代碼的常見技巧和誤區(qū)。


碼世界.jpg

通用技巧
單一職責(zé)
這是整潔代碼的最重要也是最基本的原則了罪既。簡(jiǎn)單來(lái)講铸题,大到一個(gè)module、一個(gè)package琢感,小到一個(gè)class丢间、一個(gè)method乃至一個(gè)屬性,都應(yīng)該承載一個(gè)明確的職責(zé)驹针。要定義的東西烘挫,如果不能用一句話描述清楚職責(zé),就把它拆掉柬甥。

我們平時(shí)寫代碼時(shí)饮六,最容易犯的錯(cuò)誤是:一個(gè)方法干了好幾件事或者一個(gè)類承載了許多功能。

先來(lái)聊聊方法的問(wèn)題苛蒲。個(gè)人非常主張把方法拆細(xì)喜滨,這是復(fù)用的基礎(chǔ)。如果方法干了兩件事情撤防,很有可能其中一個(gè)功能的其他業(yè)務(wù)有差別就不好重用了虽风。另外語(yǔ)義也是不明確的。經(jīng)臣脑拢看到一個(gè)get()方法里面竟然修改了數(shù)據(jù)辜膝,這讓使用你方法的人情何以堪?如果不點(diǎn)進(jìn)去看看實(shí)現(xiàn)漾肮,可能就讓程序陷入bug厂抖,讓測(cè)試陷入麻煩。

再來(lái)聊聊類的問(wèn)題克懊。我們經(jīng)常會(huì)看到“又臭又長(zhǎng)”的service/biz層的代碼忱辅,里面有幾十個(gè)方法,干什么的都有:既有增刪改查谭溉,又有業(yè)務(wù)邏輯的聚合墙懂。每次找到一個(gè)方法都費(fèi)勁。不屬于一個(gè)領(lǐng)域或者一個(gè)層次的功能扮念,就不要放到一起损搬。

我們team在code review中,最常被批評(píng)的問(wèn)題,就是一個(gè)方法應(yīng)該歸屬于哪個(gè)類巧勤。

優(yōu)先定義整體框架
我寫代碼的時(shí)候嵌灰,比較喜歡先去定義整體的框架,就是寫很多空實(shí)現(xiàn)颅悉,來(lái)把整體的業(yè)務(wù)流程穿起來(lái)沽瞭。良好的方法簽名,用入?yún)⒑统鰠?lái)控制流程剩瓶。這樣能夠避免陷入業(yè)務(wù)細(xì)節(jié)無(wú)法自拔驹溃。在腦海中先定義清楚流程的幾個(gè)階段,并為每個(gè)階段找到合適的方法/類歸屬儒搭。

這樣做的好處是,閱讀你代碼的人芙贫,無(wú)論讀到什么深度搂鲫,都可以清晰地了解每一層的職能,如果不care下一層的實(shí)現(xiàn)磺平,完全可以跳過(guò)不看魂仍,并且方法的粒度也會(huì)恰到好處。

簡(jiǎn)而言之拣挪,我比較推崇寫代碼的時(shí)候“廣度優(yōu)先”而不是“深度優(yōu)先”擦酌,這和我讀代碼的方式是一致的。當(dāng)然菠劝,這件事情跟個(gè)人的思維習(xí)慣有一定的關(guān)系赊舶,可能對(duì)抽象思維能力要求會(huì)更高一些。如果開始寫代碼的時(shí)候這些不夠清晰赶诊,起碼要通過(guò)不斷地重構(gòu)笼平,使代碼達(dá)到這樣的成色。

清晰的命名
老生常談的話題舔痪,這里不展開講了寓调,但是必須要mark一下。有的時(shí)候锄码,我思考一個(gè)方法命名的時(shí)間夺英,比寫一段代碼的時(shí)間還長(zhǎng)。原因還是那個(gè)邏輯:每當(dāng)你寫出一個(gè)類似于"temp"滋捶、"a"痛悯、"b"這樣變量的時(shí)候,后面每一個(gè)維護(hù)代碼的人重窟,都需要用幾倍的精力才能理順灸蟆。

并且這也是代碼自描述最重要的基礎(chǔ)。

避免過(guò)長(zhǎng)參數(shù)
如果一個(gè)方法的參數(shù)長(zhǎng)度超過(guò)4個(gè),就需要警惕了炒考。一方面可缚,沒(méi)有人能夠記得清楚這些函數(shù)的語(yǔ)義;另一方面斋枢,代碼的可讀性會(huì)很差帘靡;最后,如果參數(shù)非常多瓤帚,意味著一定有很多參數(shù)描姚,在很多場(chǎng)景下,是沒(méi)有用的戈次,我們只能構(gòu)造默認(rèn)值的方式來(lái)傳遞轩勘。

解決這個(gè)問(wèn)題的方法很簡(jiǎn)單,一般情況下我們會(huì)構(gòu)造paramObject怯邪。用一個(gè)struct或者一個(gè)class來(lái)承載數(shù)據(jù)绊寻,一般這種對(duì)象是value object,不可變對(duì)象悬秉。這樣澄步,能極大程度提高代碼的可復(fù)用性和可讀性。在必要的時(shí)候和泌,提供合適的build方法村缸,來(lái)簡(jiǎn)化上層代碼的開發(fā)成本。

避免過(guò)長(zhǎng)方法和類
一個(gè)類或者方法過(guò)長(zhǎng)的時(shí)候武氓,讀者總是很崩潰的梯皿。簡(jiǎn)單地把方法、類和職責(zé)拆細(xì)县恕,往往會(huì)有立竿見影的成效索烹。以類為例,拆分的維度有很多弱睦,常見的是橫向/縱向百姓。例如,如果一個(gè)service况木,處理的是跟一個(gè)庫(kù)表對(duì)象相關(guān)的所有邏輯垒拢,橫向拆分就是根據(jù)業(yè)務(wù),把建立/更新/修改/通知等邏輯拆到不同的類里去火惊;而縱向拆分求类,指的是
把數(shù)據(jù)庫(kù)操作/MQ操作/Cache操作/對(duì)象校驗(yàn)等,拆到不同的對(duì)象里去屹耐,讓主流程盡量簡(jiǎn)單可控尸疆,讓同一個(gè)類,表達(dá)盡量同一個(gè)維度的東西。

讓相同長(zhǎng)度的代碼段表示相同粒度的邏輯
這里想表達(dá)的是寿弱,盡量多地去抽取private方法犯眠,讓代碼具有自描述的能力。舉個(gè)簡(jiǎn)單的例子

   public void doSomeThing(Map params1,Map params2){
   Do1 do1 = getDo1(params1);
   Do2 do2 = new Do2();
   do2.setA(params2.get("a"));
   do2.setB(params2.get("b"));
   do2.setC(params2.get("c"));
   mergeDO(do1,do2);
   }
   private void getDo1(Map params1);
   private void mergeDo(do1,do2){...};

類似這種代碼症革,在業(yè)務(wù)代碼中隨處可見筐咧。獲取do1是一個(gè)方法,merge是一個(gè)方法噪矛,但獲取do2的代碼卻在主流程里寫了量蕊。這種代碼,流程越長(zhǎng)艇挨,讀起來(lái)越累残炮。很多人讀代碼的邏輯,是“廣度優(yōu)先”的缩滨。先讀懂主流程势就,再去看細(xì)節(jié)。類似這種代碼楷怒,如果能夠把構(gòu)造do2的代碼蛋勺,提取一個(gè)private 方法瓦灶,就會(huì)舒服很多鸠删。

面向?qū)ο笤O(shè)計(jì)技巧
貧血與領(lǐng)域驅(qū)動(dòng)
不得不承認(rèn),Spring已經(jīng)成為企業(yè)級(jí)Java開發(fā)的事實(shí)標(biāo)準(zhǔn)贼陶。而大部分公司采用的三層/四層貧血模型刃泡,已經(jīng)讓我們的編碼習(xí)慣,變成了面向DAO而不是面向?qū)ο蟆?/p>

缺少了必要的模型抽象和設(shè)計(jì)環(huán)節(jié)碉怔,使得代碼冗長(zhǎng)烘贴,復(fù)用程度比較差。每次擼代碼的時(shí)候撮胧,從mapper擼起桨踪,好像已經(jīng)成為不成文的規(guī)范。

好處是上手簡(jiǎn)單芹啥,學(xué)習(xí)成本低锻离。但是每次都不能重用,然后面對(duì)兩三千行的類看著眼花的時(shí)候墓怀,我的心是很痛的汽纠。關(guān)于領(lǐng)域驅(qū)動(dòng)的設(shè)計(jì)模式,本文不會(huì)展開去講傀履∈洌回歸面向?qū)ο螅€是跟大家share一些比較好的code技巧,能夠在一個(gè)通用的框架下碴犬,盡量好的寫出漂亮可重用的code絮宁。

個(gè)人認(rèn)為,一個(gè)好的系統(tǒng)翅敌,一定離不開一套好的模型定義羞福。梳理清楚系統(tǒng)中的核心模型,清楚的定義每個(gè)方法的類歸屬蚯涮,無(wú)論對(duì)于代碼的可讀性治专、可交流性,還是和產(chǎn)品的溝通遭顶,都是有莫大好處的张峰。

為每個(gè)方法找到合適的類歸屬,數(shù)據(jù)和行為盡量要在一起
如果一個(gè)類的所有方法棒旗,都是在操作另一個(gè)類的對(duì)象喘批。這時(shí)候就要仔細(xì)想一想類的設(shè)計(jì)是否合理了。理論上講铣揉,面向?qū)ο蟮脑O(shè)計(jì)饶深,主張數(shù)據(jù)和行為在一起。這樣逛拱,對(duì)象之間的結(jié)構(gòu)才是清晰的敌厘,也能減少很多不必要的參數(shù)傳遞。

不過(guò)這里面有一個(gè)要討論的方法:service對(duì)象朽合。如果操作一個(gè)對(duì)象數(shù)據(jù)的所有方法都建立在對(duì)象內(nèi)部俱两,可能使對(duì)象承載了很多并不屬于它本身職能的方法。

例如曹步,我定義一個(gè)類宪彩,叫做person,讲婚。這個(gè)類有很多行為尿孔,比如:吃飯、睡覺(jué)筹麸、上廁所活合、生孩子;也有很多字段竹捉,比如:姓名芜辕、年齡、性格块差。

很明顯侵续,字段從更大程度上來(lái)講倔丈,是定義和描述我這個(gè)人的,但很多行為和我的字段并不相關(guān)状蜗。上廁所的時(shí)候是不會(huì)關(guān)心我是幾歲的需五。如果把所有關(guān)于人的行為全部在person內(nèi)部承載,這個(gè)類一定會(huì)膨脹的不行轧坎。

這時(shí)候就體現(xiàn)了service方法的價(jià)值宏邮,如果一個(gè)行為,無(wú)法明確屬于哪個(gè)領(lǐng)域?qū)ο蟾籽瑺繌?qiáng)地融入領(lǐng)域?qū)ο罄锩郯保瑫?huì)顯得很不自然。這時(shí)候捎泻,無(wú)狀態(tài)的service可以發(fā)揮出它的作用飒炎。但一定要把握好這個(gè)度,回歸本質(zhì)笆豁,我們要把屬于每個(gè)模型的行為合理的去劃定歸屬郎汪。

警惕static
static方法,本質(zhì)上來(lái)講是面向過(guò)程的闯狱,無(wú)法清晰地反饋對(duì)象之間的關(guān)系煞赢。雖然有一些代碼實(shí)例(自己實(shí)現(xiàn)單例或者Spring托管等)的無(wú)狀態(tài)方法可以用static來(lái)表示,但這種抽象是淺層次的哄孤。說(shuō)白了照筑,如果我們所有調(diào)用static的地方,都寫上import static录豺,那么所有的功能就由類自己在承載了朦肘。

讓我畫一個(gè)類圖饭弓?尷尬了……畫不出來(lái)双饥。

而單例的膨脹,很大程度上也是貧血模型帶來(lái)的副作用弟断。如果對(duì)象本身有血有肉咏花,就不需要這么多無(wú)狀態(tài)方法。

static真正適用的場(chǎng)景:工具方法阀趴,而不是業(yè)務(wù)方法昏翰。

巧用method object
method object是大型重構(gòu)的常用技巧。當(dāng)一段邏輯特別復(fù)雜的代碼刘急,充斥著各種參數(shù)傳遞和是非因果判斷的時(shí)候棚菊,我首先想到的重構(gòu)手段是提取method object。所謂method object叔汁,是一個(gè)有數(shù)據(jù)有行為的對(duì)象统求。依賴的數(shù)據(jù)會(huì)成為這個(gè)對(duì)象的變量检碗,所有的行為會(huì)成為這個(gè)對(duì)象的內(nèi)部方法。利用成員變量代替參數(shù)傳遞码邻,會(huì)讓代碼簡(jiǎn)潔清爽很多折剃。并且,把一段過(guò)程式的代碼轉(zhuǎn)換成對(duì)象代碼像屋,為很多面向?qū)ο缶幊滩趴梢允褂玫睦^承/封裝/多態(tài)等提供了基礎(chǔ)怕犁。

舉個(gè)例子,上文引用的代碼如果用method object表示大概會(huì)變成這樣

 class DoMerger{
    map params1;
    map params2;
    Do1 do1;
    Do2 do2;
    public DoMerger(Map params1,Map params2){
       this.params1 = params1;
       this.params2 = parmas2;
    }
    public void invoke(){
        do1 = getDo1();
        do2 = getDo2();
       mergeDO(do1,do2);
    }
    private Do1 getDo1();
     private Do2 getDo2();
     private void mergeDo(){
        print(do1+do2);
     }
 }

面向接口編程
面向接口編程是很多年來(lái)大家形成的共識(shí)和最佳實(shí)踐己莺。最早的理論是便于實(shí)現(xiàn)的替換奏甫,但現(xiàn)在更顯而易見的好處是避免public方法的膨脹。一個(gè)對(duì)外publish的接口凌受,一定有明確的職責(zé)扶檐。要判斷每一個(gè)public方法是否應(yīng)該屬于同一個(gè)interface,是很容易的胁艰。

整個(gè)代碼基于接口去組織款筑,會(huì)很自然地變得非常清晰易讀。關(guān)注實(shí)現(xiàn)的人才去看實(shí)現(xiàn)腾么,不是嘛奈梳?

正確使用繼承和組合
這也是個(gè)在業(yè)界被討論過(guò)很久的問(wèn)題,也有很多論調(diào)解虱。最新的觀點(diǎn)是組合的使用一般情況下比繼承更為靈活攘须,尤其是單繼承的體系里,所以傾向于使用組合
殴泰,否則會(huì)讓子類承載很多不屬于自己的職能于宙。

個(gè)人對(duì)此觀點(diǎn)持保留意見,在我經(jīng)歷過(guò)的代碼中悍汛,有一個(gè)小規(guī)律捞魁,我分析一下。

protected abstract 這種是最值得使用繼承的离咐,父類保留擴(kuò)展點(diǎn)谱俭,子類擴(kuò)展,沒(méi)什么好說(shuō)的宵蛀。

protected final 這種方法昆著,子類是只能使用不能修改實(shí)現(xiàn)的。一般有兩種情況:
① 抽象出主流程不能被修改的术陶,然而一般情況下凑懂,public final更適合這個(gè)職能。如果只是流程的一部分梧宫,需要思考這個(gè)流程的類歸屬接谨,大部分變成public組合到其他類里是更合適的杭攻。
② 父類是抽象類無(wú)法直接對(duì)外提供服務(wù),又不希望子類修改它的行為疤坝,這種大多數(shù)情況下屬于工具方法兆解,比較適合用另一個(gè)領(lǐng)域?qū)ο髞?lái)承載并用組合的方式來(lái)使用。

protected 這種是有爭(zhēng)議的跑揉,是父類有默認(rèn)實(shí)現(xiàn)但子類可以擴(kuò)展的锅睛。凡是有擴(kuò)展可能的,使用繼承更理想一些历谍。否則现拒,定義成final并考慮成組合。

綜上所述望侈,個(gè)人認(rèn)為繼承更多的是為擴(kuò)展提供便利印蔬,為復(fù)用而存在的方法最好使用組合的方式。當(dāng)然脱衙,更為大的原則是明確每個(gè)方法的領(lǐng)域劃分侥猬。

代碼復(fù)用技巧
模板方法
這是我用得最多的設(shè)計(jì)模式了。每當(dāng)有兩個(gè)行為類似但又不完全相同的代碼段時(shí)捐韩,我總是會(huì)想到模板方法退唠。提取公共流程和可復(fù)用的方法到父類,保留不同的地方作為abstract方法荤胁,由不同的子類去實(shí)現(xiàn)瞧预。

并在合適的時(shí)機(jī),pull method up(復(fù)用)或者 pull method down(特殊邏輯)仅政。

最后垢油,把不屬于流程的、但可復(fù)用的方法圆丹,判斷是不是屬于基類的領(lǐng)域職責(zé)滩愁,再使用繼承或者組合的方法,為這些方法找到合適的安家之處运褪。

extract method
很多復(fù)用的級(jí)別沒(méi)有這么大惊楼,也許只是幾行相同的邏輯被copy了好幾次玖瘸,何不嘗試提取方法(private)秸讹。又能明確方法行為,又能做到代碼復(fù)用雅倒,何樂(lè)不為璃诀?

責(zé)任鏈
經(jīng)常看到這樣的代碼蔑匣,一連串類似的行為劣欢,只是數(shù)據(jù)或者行為不一樣棕诵。如一堆校驗(yàn)器,如果成功怎么樣凿将、失敗怎么樣校套;或者一堆對(duì)象構(gòu)建器,各去構(gòu)造一部分?jǐn)?shù)據(jù)牧抵。碰到這種場(chǎng)景笛匙,我總是喜歡定義一個(gè)通用接口,入?yún)⑹峭暾囊r?yàn)/構(gòu)造的參數(shù)犀变,
出參是成功/失敗的標(biāo)示或者是void妹孙。然后有很多實(shí)現(xiàn)器分別實(shí)現(xiàn)這個(gè)接口,再用一個(gè)集合把這堆行為串起來(lái)获枝。最后蠢正,遍歷這個(gè)集合,串行或者并行的執(zhí)行每一部分的邏輯省店。

這樣做的好處是:
① 很多通用的代碼可以在責(zé)任鏈原子對(duì)象的基類里實(shí)現(xiàn)嚣崭;
② 代碼清晰,開閉原則懦傍,每當(dāng)有新的行為產(chǎn)生的時(shí)候有鹿,只需要定義行的實(shí)現(xiàn)類并添加到集合里即可;
③ 為并行提供了基礎(chǔ)谎脯。

為集合顯式定義它的行為
集合是個(gè)有意思的東西葱跋,本質(zhì)上它是個(gè)容器,但由于泛型的存在源梭,它變成了可以承載所有對(duì)象的容器娱俺。很多非集合的類,我們可以定義清楚他們的邊界和行為劃分废麻,但是裝進(jìn)集合里荠卷,它們卻都變成了一個(gè)樣子。不停地有代碼烛愧,各種循環(huán)集合油宜,做一些相似的操作。

其實(shí)很多時(shí)候怜姿,可以把對(duì)集合的操作顯示地封裝起來(lái)慎冤,讓它變得更有血有肉。

例如一個(gè)Map沧卢,它可能表示一個(gè)配制蚁堤、一個(gè)緩存等等。如果所有的操作都是直接操作Map但狭,那么它的行為就沒(méi)有任何語(yǔ)義披诗。第一撬即,讀起來(lái)就必須要深入細(xì)節(jié);第二呈队,如果想從獲取配置讀取緩存的地方加個(gè)通用的邏輯剥槐,例如打個(gè)log什么的,你可以想象是多么的崩潰宪摧。

個(gè)人提倡的做法是才沧,對(duì)于有明確語(yǔ)義的集合的一些操作,尤其是全局的集合或者被經(jīng)常使用的集合绍刮,做一些封裝和抽象温圆,如把Map封裝成一個(gè)Cache類或者一個(gè)config類,再提供GetFromCache這樣的方法孩革。

總結(jié)
本文從clean code的幾個(gè)大前提出發(fā)岁歉,然后提出了實(shí)踐clean code的一些手段,重點(diǎn)放在促成clean code的一些常用編碼和重構(gòu)技巧膝蜈。
當(dāng)然锅移,這些只代表筆者本人的一點(diǎn)點(diǎn)感悟。好的代碼饱搏,最最需要的非剃,還是大家不斷追求卓越的精神。歡迎大家一起探索交流這個(gè)領(lǐng)域推沸,為clean code提供更多好的思路與方法备绽。

源代碼.jpg

注: 本文來(lái)源美團(tuán)點(diǎn)評(píng)技術(shù)團(tuán)隊(duì)


我是楚簡(jiǎn)約,感謝您的閱讀鬓催,

喜歡就點(diǎn)個(gè)贊唄肺素,“?喜歡”,

鼓勵(lì)又不花錢宇驾,你在看倍靡,我就繼續(xù)寫~

非簡(jiǎn)書用戶,可以點(diǎn)右上角的三個(gè)“...”课舍,然后"在Safari中打開”塌西,就可以點(diǎn)贊咯~


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市筝尾,隨后出現(xiàn)的幾起案子捡需,更是在濱河造成了極大的恐慌,老刑警劉巖忿等,帶你破解...
    沈念sama閱讀 212,029評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件栖忠,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡贸街,警方通過(guò)查閱死者的電腦和手機(jī)庵寞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)薛匪,“玉大人捐川,你說(shuō)我怎么就攤上這事∫菁猓” “怎么了古沥?”我有些...
    開封第一講書人閱讀 157,570評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)娇跟。 經(jīng)常有香客問(wèn)我岩齿,道長(zhǎng),這世上最難降的妖魔是什么苞俘? 我笑而不...
    開封第一講書人閱讀 56,535評(píng)論 1 284
  • 正文 為了忘掉前任盹沈,我火速辦了婚禮,結(jié)果婚禮上吃谣,老公的妹妹穿的比我還像新娘乞封。我一直安慰自己,他們只是感情好岗憋,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評(píng)論 6 386
  • 文/花漫 我一把揭開白布肃晚。 她就那樣靜靜地躺著,像睡著了一般仔戈。 火紅的嫁衣襯著肌膚如雪关串。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,850評(píng)論 1 290
  • 那天监徘,我揣著相機(jī)與錄音悍缠,去河邊找鬼。 笑死耐量,一個(gè)胖子當(dāng)著我的面吹牛飞蚓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播廊蜒,決...
    沈念sama閱讀 39,006評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼趴拧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了山叮?” 一聲冷哼從身側(cè)響起著榴,我...
    開封第一講書人閱讀 37,747評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎屁倔,沒(méi)想到半個(gè)月后脑又,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,207評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評(píng)論 2 327
  • 正文 我和宋清朗相戀三年问麸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蜈漓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蔗草。...
    茶點(diǎn)故事閱讀 38,683評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出茴迁,到底是詐尸還是另有隱情谎势,我是刑警寧澤菇曲,帶...
    沈念sama閱讀 34,342評(píng)論 4 330
  • 正文 年R本政府宣布嗡午,位于F島的核電站,受9級(jí)特大地震影響稠肘,放射性物質(zhì)發(fā)生泄漏福铅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評(píng)論 3 315
  • 文/蒙蒙 一项阴、第九天 我趴在偏房一處隱蔽的房頂上張望滑黔。 院中可真熱鬧,春花似錦鲁冯、人聲如沸拷沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)撞芍。三九已至,卻和暖如春跨扮,著一層夾襖步出監(jiān)牢的瞬間序无,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工衡创, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帝嗡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,401評(píng)論 2 360
  • 正文 我出身青樓璃氢,卻偏偏與公主長(zhǎng)得像哟玷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子一也,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容