我們一起聊聊如何寫出一手好代碼
無論是剛?cè)胄械男率诌€是已經(jīng)工作多年的老司機,都希望自己可以寫一手好代碼,這樣在代碼CR的時候就可以悄悄驚艷所有人。特別是對于剛?cè)肼毜男峦瑢W(xué)來說,代碼寫得好可以幫助自己在新環(huán)境快速建立技術(shù)影響力。因為對于從事IT互聯(lián)網(wǎng)研發(fā)工作的同學(xué)來說,技術(shù)能力是研發(fā)同學(xué)的立身之本,而寫代碼的能力又是技術(shù)能力的重要體現(xiàn)。但可惜的是理想很豐滿,現(xiàn)實很骨感。結(jié)合慕楓自己的經(jīng)驗來看,我們在工作中其實沒那么容易可以看到寫得很好的代碼。造成這種情況的原因也許很多,但是無論什么原因都不應(yīng)該妨礙我們對于寫好代碼的追求。今天慕楓就和大家探討下到底怎樣做才能寫出一手大家都認(rèn)為好的代碼?
哪些因素制約好代碼的產(chǎn)生?
我們首先來分析下到底哪些因素造成了現(xiàn)實工作中好代碼難以產(chǎn)出。因為只有搞清楚了這個問題才能對癥下藥,這樣在我們自己寫代碼的時候才能盡量避免這些問題影響我們寫好代碼。
假如讓我們說出哪些是爛代碼,我們也許會羅列出來代碼不易理解、沒有注釋、方法或者類詞不達意、分層不合理、不夠抽象、單個方法過長、單個類過長、代碼難以維護每次改動都牽一發(fā)動全身、重復(fù)代碼過多等等,這些都是我們在實際項目開發(fā)過長中經(jīng)常遇到的代碼問題。那么到底是什么原因造成了現(xiàn)實項目中有這么多的代碼問題呢?慕楓認(rèn)為主要存在以下三方面的原因。
圖片
1、項目倒排時間不夠
項目需求倒排導(dǎo)致沒有時間在寫代碼前好好進行設(shè)計,所以只能先快速滿足需求等后面有時間再優(yōu)化(大概率是沒有時間的)。這就造成技術(shù)同學(xué)在寫代碼的時候怎么快怎么寫,優(yōu)先把功能實現(xiàn)了再說,很多該考慮的細(xì)節(jié)就不會考慮那么多,該處理的異常沒有進行處理,所以可能寫出來的代碼可以說是一次性代碼,只針對當(dāng)前的業(yè)務(wù)場景,基本沒什么擴展性可言。
2、團隊技術(shù)氛圍不足
團隊內(nèi)技術(shù)氛圍不是很濃厚,本來你是想好好把代碼寫好的,但是發(fā)現(xiàn)大家都在短平快的寫代碼,而且沒有太多人關(guān)心代碼寫的好不好,只關(guān)心需求有沒有按時完成。在這樣的團隊氛圍影響之下,自己寫出來的代碼也在慢慢地妥協(xié)。像在阿里這樣的一線互聯(lián)網(wǎng)公司,團隊中的代碼文化還是很強的,很多技術(shù)團隊在需求上線前必須要進行代碼CR,CR不過的代碼不允許上線。因此好的團隊技術(shù)氛圍會促使你不得不把代碼寫好,否則在代碼CR的時候就等著接受暴風(fēng)雨般的吐槽吧。
3、自身技術(shù)水平有限
第三個原因就是自身的技術(shù)水平有限,設(shè)計模式不知道該在什么樣的業(yè)務(wù)場景下使用,框架的高級用法沒有掌握,經(jīng)驗不足導(dǎo)致異常情況經(jīng)常考慮不到。自己本身沒有把代碼寫好的追求,總想著能滿足需求代碼能跑就行。
以上大概是我們實際工作中導(dǎo)致我們不能產(chǎn)出好代碼最主要的三大原因,第一個原因我們基本無法改變,因為在互聯(lián)網(wǎng)行業(yè)競爭本身就非常激烈,誰能先推出新業(yè)務(wù)優(yōu)化用戶體驗,誰就能占得市場先機。因此項目倒排必定是常有的事情,也是無法避免的事情。第二個原因,如果你自己是團隊的TL,那么盡量在團隊中去營造代碼CR的文化,提升團隊中的技術(shù)氛圍。因為代碼是技術(shù)團隊的根本,所有的業(yè)務(wù)效果落地都需要通過代碼來實現(xiàn),因此好的代碼可以幫助團隊減少Bug出現(xiàn)的概率、提升大家的代碼效率從而達到降低人力物力成本的目的。如果你不是團隊的TL,同時團隊中的技術(shù)氛圍也沒那么足,那么我們也不要放棄治療,先把自己負(fù)責(zé)的模塊的代碼寫好,一點點影響團隊,逐漸喚起大家對于好代碼的重視。
前兩個因素都屬于環(huán)境因素,也許我們不好改變,但是對于第三個因素,我覺得我們可以通過理論知識的學(xué)習(xí),不斷的代碼實踐以及思考總結(jié)是可以改變的,因此本文主要還是討論如何通過改變自己來把代碼寫好。
到底什么是好代碼?
要想寫出好的代碼,首先我們得知道什么樣的代碼才是好代碼。但是好這個字本身就具有較強的主觀性,正所謂一千個讀者心中就有一千個哈姆雷特。因此我們需要先統(tǒng)一一下好代碼的標(biāo)準(zhǔn),有了標(biāo)準(zhǔn)之后我們再來探討到底怎么做才能寫出好代碼。
我相信大家肯定聽說過代碼可讀性、代碼擴展性、可維護性等詞匯來描述好代碼的特點,實際上這些形容詞都是從不同方面對代碼進行了闡述。但是在慕楓看來,在實際的項目開發(fā)中,可維護性以及高魯棒性是好代碼的兩個比較核心的衡量標(biāo)準(zhǔn)。因為無論是開發(fā)新需求還是修復(fù)Bug,都是在原有的平臺代碼中進行修改,如果原來代碼的擴展性比較強,那么我們編碼的時候就就可以做到最小化修改,降低引入問題的風(fēng)險。而魯棒性高的代碼在線上出現(xiàn)Bug的概率相對來說就第一點,對于維護線上服務(wù)的穩(wěn)定性具有重要意義。
可維護性
我們都知道代碼開發(fā)并不是一個人的工作,通常涉及到很多人團隊合作。因此慕楓認(rèn)為代碼的可維護性是好代碼的第一要義。而可維護性主要體現(xiàn)在代碼可讀容易理解以及修改方便容易擴展這兩方面,下面分別進行闡述說明。
代碼可讀
我們寫出來的代碼不僅僅要自己能看得懂自己寫的代碼,別人也應(yīng)該可以輕松看得懂你的代碼。在一線的互聯(lián)網(wǎng)大廠中工作內(nèi)容發(fā)生變化是常有的事情,如果別人接手我們的代碼或者我們接手別人的代碼時,可讀性強的代碼無疑可以減少大家理解業(yè)務(wù)的時間成本。因為代碼是最直接的業(yè)務(wù)表現(xiàn),那些所謂的設(shè)計文檔要么過時要么寫的非常粗略,基本不太能指導(dǎo)我們熟悉業(yè)務(wù)。那么什么樣的代碼稱得上可讀性強呢?
命名準(zhǔn)確
無論是包的命名、類的命名、方法的命名還是變量的命名都能很準(zhǔn)確地表達業(yè)務(wù)含義,讓人可以看其名知其義。命名應(yīng)該和實際的代碼邏輯相匹配,否則不合適的命名只會讓人丈二和尚摸不著腦袋誤導(dǎo)看代碼的同學(xué)。以前看代碼的時候我看過以main作為類中的方法名稱,所以得看完這個方法的實現(xiàn)邏輯才能明白它到底干什么的,這對于后期維護的同學(xué)來說非常不友好。
代碼注釋
另外就是必要的注釋,有些同學(xué)非常自信覺得自己寫的代碼很好懂,根本不需要寫什么注釋。結(jié)果自己過了一兩個月再回頭看自己的代碼的時候,死活想不起來某段代碼為什么要這么寫。當(dāng)然我們不必每一行代碼都寫注釋,但是該注釋的地方就要寫注釋,特別是一些邏輯比較復(fù)雜,業(yè)務(wù)性比較強的地方,既方便自己以后排查問題也方便后面維護的同學(xué)理解業(yè)務(wù)。因此不要對自己寫的代碼過于自信,間隔時間一長也許連你自己都未必記得代碼為什么這么寫。
結(jié)構(gòu)清晰
無論是服務(wù)的包結(jié)構(gòu)還是代碼結(jié)構(gòu)都體現(xiàn)了技術(shù)同學(xué)對于技術(shù)的理解,因此即便是不深入看代碼邏輯,通過包結(jié)構(gòu)的劃分、模塊的劃分類結(jié)構(gòu)的設(shè)計已經(jīng)基本可以判斷出來項目的代碼質(zhì)量了。我們在進行包結(jié)構(gòu)設(shè)計的時候可以遵循依賴倒置的原則,讓非核心層依賴核心層。
圖片
可擴展性
隨著業(yè)務(wù)需求的不斷變化,技術(shù)同學(xué)免不了在原有的代碼邏輯中進行修改。因此項目代碼的可擴展性直接影響著后期維護的成本。如果改一個小需求就需要對原有的代碼大動干戈,修改的地方越多引入Bug的風(fēng)險就會越大。我們都知道線上的故障有七八成都是由于變更引起的,因此可擴展性強的代碼可以有效控制變更的范圍。
高魯棒性
當(dāng)我們說到代碼魯棒性高的時候,實際就是說代碼比較健壯,能夠應(yīng)對各種輸入,即便出現(xiàn)異常也會有對應(yīng)的異常處理機制進行響應(yīng)而不至于直接崩潰。而項目開發(fā)不是一個人的工作,通常都是團隊合作,因此我們寫的代碼無時無刻不在和別人的代碼進行交互,所以我們負(fù)責(zé)的代碼模塊總是在處理可能正常可能異常的輸入。如果不能對可能出現(xiàn)的異常輸入進行妥善的防御性處理,那么可能就會造成Bug的產(chǎn)生,嚴(yán)重情況下甚至?xí)绊懴到y(tǒng)正常運行。因此好的代碼除了方便擴展方便維護之外,它必定也是高魯棒性的,否則如果每天Bug滿天飛,哪有時間和精力去琢磨代碼的可擴展性,大部分精力都用來修復(fù)Bug,長此以往自己也會感覺身心俱疲,總是感覺自己沒什么成長。
如何寫出好代碼?
強烈內(nèi)在驅(qū)動
為什么我把強烈的內(nèi)在驅(qū)動擺在首要位置,主要是因為我覺得程序員只有有了想把代碼寫好的愿望,才能真正驅(qū)動自己寫出來好代碼。否則即便掌握了各種設(shè)計原則以及優(yōu)化技巧,但是自己沒有寫好代碼的內(nèi)在驅(qū)動,總是覺得程序又不是不能用,或者覺得代碼和自己有一個能跑就行,亦或是抱著后面有時間再優(yōu)化的態(tài)度(基本是沒時間)是不可能寫好代碼的。因此首先我們得有寫好代碼的內(nèi)在驅(qū)動和愿望,我們才能有把代碼寫好的可能。不過話又說回來,內(nèi)在驅(qū)動是基礎(chǔ),全是感情沒有技巧肯定也不行。
沉淀業(yè)務(wù)模型
談完了內(nèi)在驅(qū)動這個感情,我們就要來看看要掌握哪些技巧才能幫助我們寫出來好代碼,首當(dāng)其沖的就是業(yè)務(wù)領(lǐng)域模型,因為它是領(lǐng)域業(yè)務(wù)在工程代碼中的落地也是整個服務(wù)的核心,不過遺憾的是很多同學(xué)并沒有意識到它的重要性,甚至經(jīng)常會把數(shù)據(jù)模型和業(yè)務(wù)模型相混淆。而我自己在在團隊中落地DDD領(lǐng)域驅(qū)動設(shè)計的時候,被技術(shù)同學(xué)問過比較多的問題就是數(shù)據(jù)庫表對應(yīng)的數(shù)據(jù)實體滿足不了業(yè)務(wù)需要嗎?為什么還需要業(yè)務(wù)領(lǐng)域模型?那么想要回答這些問題,我們得先搞清楚到底什么是領(lǐng)域模型,它到底能給技術(shù)團隊帶來什么。
從本質(zhì)上來說領(lǐng)域模型就是我們對于本行業(yè)業(yè)務(wù)領(lǐng)域的認(rèn)知,體現(xiàn)了你對行業(yè)認(rèn)知的沉淀以及外化表現(xiàn)。那么怎么體現(xiàn)你對行業(yè)領(lǐng)域業(yè)務(wù)認(rèn)知的深度呢?領(lǐng)域模型就是很好的驗證手段,對行業(yè)認(rèn)知越深刻的同學(xué)構(gòu)建的領(lǐng)域模型越能夠刻畫現(xiàn)實中的業(yè)務(wù)場景,我們也可以認(rèn)為領(lǐng)域模型是現(xiàn)實世界業(yè)務(wù)場景到代碼世界的映射,同時它也是公司重要的業(yè)務(wù)資產(chǎn)。那么每個行業(yè)的業(yè)務(wù)認(rèn)知又是從哪里來的呢?實際上就從實際的業(yè)務(wù)場景中抽象出來的。所以領(lǐng)域模型的建立通常都是伴隨著業(yè)務(wù)需求的出現(xiàn)。因此領(lǐng)域模型是核心,包含了業(yè)務(wù)概念以及概念之間的關(guān)系,它可以幫助團隊統(tǒng)一認(rèn)識以及指導(dǎo)設(shè)計。
圖片
但是領(lǐng)域建模具有一定的門檻,其中包含了很多難以理解的概念,這也造成了在很多技術(shù)團隊中難以落地。但是在阿里等國內(nèi)一線互聯(lián)網(wǎng)公司卻有著廣泛的應(yīng)用,因為DDD領(lǐng)域驅(qū)動設(shè)計可以指導(dǎo)我們應(yīng)對復(fù)雜系統(tǒng)的設(shè)計開發(fā),控制系統(tǒng)復(fù)雜度,幫助我們劃分業(yè)務(wù)域,將業(yè)務(wù)模型域?qū)崿F(xiàn)細(xì)節(jié)相分離。所以慕楓覺得讓大家認(rèn)識到DDD領(lǐng)域驅(qū)動設(shè)計以及領(lǐng)域模型的的重要性比如何玩轉(zhuǎn)DDD本身更加重要。
圖片
另外在這里不得不提一下數(shù)據(jù)模型和領(lǐng)域模型的區(qū)別,在實際的工作中我發(fā)現(xiàn)很多同學(xué)都容易將這兩者混淆。領(lǐng)域模型關(guān)注的是業(yè)務(wù)場景下的領(lǐng)域知識,是業(yè)務(wù)需求中概念以及概念之間的關(guān)系,它的存在就是顯示的精確的表達業(yè)務(wù)語義。而數(shù)據(jù)模型關(guān)注的是業(yè)務(wù)數(shù)據(jù)如何存儲,如何擴展以及如何操作性能更高。因此他們關(guān)注的層面不同,領(lǐng)域模型關(guān)注業(yè)務(wù),數(shù)據(jù)模型關(guān)心實現(xiàn)。
這里可以舉個例子給大家說明一下,假設(shè)有這樣的業(yè)務(wù)場景,告警規(guī)則中存在一個規(guī)則范圍的概念,主要可以給出不同的告警取值判斷的范圍,比如某個接口調(diào)用次數(shù)失敗的最大值,或者設(shè)備在線數(shù)量不能低于某個最小值等等,因此有了如下簡化版本的領(lǐng)域模型。
圖片
那么在實際實現(xiàn)落地的時候,就很自然想到將AlarmRule以及RuleRange分別用一個表進行進行存儲。這其實就是把領(lǐng)域模型和數(shù)據(jù)模型混淆的典型例子,實際上我們沒有必要搞兩張表來存儲,一張表其實就夠了,主要有以下兩個原因:
1、寫代碼的時候我們維護一張表肯定比維護兩張表操作起來更加方便;
2、另外萬一后面ruleRange有新的變化,增減了新的判斷條件,我們還得要修改rule_ranged字段,不利于后期的擴展。
圖片
因此我們用一張表來就進行存儲就好了,多一個json類型的字段,專門存儲閾值判斷范圍。只不過在領(lǐng)域模型中我們需要把c_rule_range定義為一個對象,這樣在代碼層面操作起來比較方便。
圖片
牢記設(shè)計原則
無論設(shè)計原則還是設(shè)計模式,都是先驅(qū)們在以往大量軟件設(shè)計開發(fā)實踐中總結(jié)出來的寶貴經(jīng)驗,因此我們在項目開發(fā)中完全可以站在巨人的肩膀上利用這些設(shè)計原則指導(dǎo)我們進行編碼。當(dāng)然如果我們想熟練使用這些設(shè)計原則,就必須先要理解他們,搞清楚這些設(shè)計原則到底是為了解決什么問題而產(chǎn)生的。
我們不妨仔細(xì)想一想,平日時間里技術(shù)同學(xué)的開發(fā)工作基本上都是在已有的服務(wù)中進行新需求開發(fā)或者在原有的邏輯中修修改改。因此如果因為一個需求需要修改原有代碼邏輯,我們總是希望修改的地方越少越好,否則如果修改的地方多了,那么引入的Bug風(fēng)險就會越大。即便是項目需要進行重構(gòu)的情況,那我們也希望重構(gòu)后的服務(wù)或者組件可以滿足高內(nèi)聚低耦合的大要求,這樣在未來進行需求開發(fā)的時候可以更加方便的進行修改。這也是我們希望我們開發(fā)的代碼高內(nèi)聚低耦合的原因。可以看得出來,設(shè)計原則的核心思想就是幫助技術(shù)人員開發(fā)的軟件平臺能夠更好地應(yīng)對各種各樣的需求變化,從而最終達到降低維護成本,提高工作效率的目的。
當(dāng)我們說到設(shè)計原則的時候,通常都會想到SOLID五大原則,這里所說的設(shè)計原則主要包括SOLID原則、迪米特法則。
單一職責(zé)原則
對于一個方法、類或者模塊來說,它的職責(zé)應(yīng)該是單一的,方法、類或者模塊應(yīng)該只負(fù)責(zé)處理一個業(yè)務(wù)。這個原則應(yīng)該很好理解,當(dāng)我們在寫代碼的時候,無論是方法、類以及模塊都應(yīng)該從功能或者業(yè)務(wù)的角度考慮將無關(guān)的邏輯抽離出去。為什么這么做呢?主要還是為了能夠?qū)崿F(xiàn)代碼業(yè)務(wù)功能的原子化操作,這樣即便未來進行修改的時候影響的范圍也會變得有限。如果我們不遵守單一職責(zé)原則,那么在修改代碼邏輯的時候很可能影響了其他業(yè)務(wù)的邏輯,造成修改影響范圍不可控的情況。
You want to isolate your modules from the complexities of the organization as a whole, and design your systems such that each module is responsible (responds to) the needs of just that one business function.
不過需要說明的是,這里的所說的單一職責(zé)是針對當(dāng)前的業(yè)務(wù)場景來說的,也許隨著業(yè)務(wù)的發(fā)展和場景的擴充,原來滿足單一職責(zé)的方法、類或者模塊可能現(xiàn)在就不滿足了需要進一步的拆分細(xì)化。
開閉原則
慕楓認(rèn)為開閉原則與其說它是一種設(shè)計原則,不如說它是一種軟件設(shè)計指導(dǎo)思想。無論我們編寫框架代碼還是業(yè)務(wù)代碼都可以在開閉原則這樣的核心思想指導(dǎo)下進行設(shè)計。
Software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification。
所謂開閉原則指的就是我們開發(fā)的框架、模塊以及類等軟件實體應(yīng)該對擴展開放,對修改關(guān)閉。這個原則看上去很容易理解,但是在進行項目實際落地的時候卻不是一件容易的事情。因為對于擴展以及修改并沒有明確的定義,到底什么樣的代碼才是擴展,什么樣的代碼才是修改?這些問題不搞清楚的話,我們很難把開閉原則落地到實際的項目開發(fā)中。
結(jié)合自己的開發(fā)經(jīng)驗可以這么理解,假設(shè)我們在項目中開發(fā)一個功能的時候,如果能做到不修改已有代碼邏輯,而是在原有代碼結(jié)構(gòu)中擴展新的模塊、類或者方法的話,那么我們認(rèn)為代碼是?開閉原則的。當(dāng)然這也不是絕對的,比如假設(shè)你修改一個原有邏輯中的判斷條件的閾值,那只能在原有代碼邏輯中進行修改。總不能因為要滿足這個原則非要搞出來。所以我覺得我們不必要教條的去追求滿足開閉原則,而是從大方向上以及整體上考慮滿足開閉原則。
里氏替換原則
在面向?qū)ο笏枷霕?gòu)建的程序中,子類對象可以替換程序中任何地方出現(xiàn)的父類對象,同時還能保證程序的邏輯不變以及正確性不變,這就是里氏替換原則的字面理解。不知道大家有沒有發(fā)現(xiàn),這個里氏替換原則看上去和Java中的多態(tài)一樣一樣的。實際上他們還是有區(qū)別的,多態(tài)是面向?qū)ο缶幊痰奶匦裕侵匾拇a實現(xiàn)思路。而里氏替換原則是一種設(shè)計原則,約定子類不能破壞父類定義好的邏輯以及異常處理。
比如在倉儲業(yè)務(wù)域中,父類中有對揀貨任務(wù)進行排序的sortPickingTaskByTime()方法,它是按照任務(wù)創(chuàng)建的時間對到來的揀貨任務(wù)進行排序,那么我們在子類實現(xiàn)的時候如果在sortPickingTaskByTime()方法內(nèi)部按照揀貨任務(wù)涉及的商品品類進行排序,那么明顯是不符合里氏替換原則的,但是從多態(tài)的角度來說或者從語法的角度來說卻沒有問題。
里氏替換原則的核心思想就是按照約定辦事,父類約定好了的行為,子類實現(xiàn)需要嚴(yán)格遵守。那么里氏替換原則對于實際編碼有什么指導(dǎo)意義呢?比如上文所說的sortPickingTaskByTime()排序方法,如果父類中的算法實現(xiàn)效率不高,我們可以在子類中進行優(yōu)化,有了里氏替換原則就可以通過子類改進當(dāng)前已有的實現(xiàn)。另外父類中的方法定義就是契約,可以指導(dǎo)我們后面的編碼。
接口隔離原則
所謂接口隔離說的是接口調(diào)用方不應(yīng)該被迫依賴它不需要的接口。怎么理解這句話呢?按照慕楓自己的理解,接口調(diào)用方只關(guān)心和自己業(yè)務(wù)相關(guān)的接口,其他不相關(guān)的接口應(yīng)該隔離到其他接口中。
Clients should not be forced to depend upon interfaces that they do not use。
從擴展能力層面來看,我們定義接口的時候按照原子能力進行定義,避免了定義一個大而全的接口,這樣在進行擴展的時候就可以按照具體的原子能力來進行,這樣無論是靈活性還是通用性上面都會更加滿足需求。
從實現(xiàn)上來說,如果實現(xiàn)方僅僅需要實現(xiàn)它以來的接口功能就好,它不需要的接口功能就不需要實現(xiàn),這樣也會大大降低代碼實現(xiàn)量。當(dāng)我們擴展或者修改代碼的時候能夠做到最小化的修改。
依賴倒置原則 依賴倒置原則不太容易理解,但是我們在實際的項目開發(fā)中卻每一天都在使用,只是我們可能沒太在意罷了。
High-level modules shouldn't depend on low-level modules. Both modules shoud depend on abstractions.In addition,abstractions shouldn't depend on details.Details depend on abstractions.
按照字面意思理解,高層級模塊不應(yīng)該依賴低層級模塊,同時兩者都應(yīng)該依賴于抽象。另外抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象。用大白話來說主要是兩個核心點,一是面向接口編程,另一個是基礎(chǔ)層依賴核心層。
面向接口編程這個應(yīng)該很好理解,因為接口定義了清晰的協(xié)議規(guī)范,研發(fā)同學(xué)可以基于接口進行開發(fā)。
圖片
迪米特法則
迪米特法則看名字是一點不知道它是干什么的,簡單來說就是類和類之間能不要有關(guān)系就不要有關(guān)系,實在沒辦法必須要有關(guān)系的那也盡量只依賴必要的接口。這樣說起來感覺還是比較抽象。看下面的圖就明白了,左邊的各個模塊拆分比較獨立,符合單一職責(zé)原則,同時模塊間只依賴它所需要的模塊,而下圖右邊的模塊拆分不夠獨立,A模塊本來只需要依賴F模塊,但是FG模塊顆粒度較大,導(dǎo)致不得不依賴G模塊的接口,顯然這是不符合迪米特法則的。
圖片
當(dāng)我們有了寫出來的代碼能夠?qū)崿F(xiàn)高內(nèi)聚低耦合、易擴展以及易維護愿景之后,那就要好好學(xué)習(xí)一些代碼實現(xiàn)的設(shè)計原則,這些設(shè)計原則在戰(zhàn)略層面可以指導(dǎo)我們擴展性強的代碼應(yīng)該往哪些方向進行設(shè)計考慮。而有了指導(dǎo)思想之后,結(jié)合不同場景下的設(shè)計模式就自然催生出來我們想要的結(jié)果。
圖片
運用設(shè)計模式
設(shè)計模式是先驅(qū)們在實踐的基礎(chǔ)上總結(jié)出來可以落地的代碼實現(xiàn)模板,針對一些業(yè)務(wù)場景提供代碼級解決方案。我們根據(jù)各個設(shè)計模式的能力特點可以將23種設(shè)計模式分類為創(chuàng)建型模式、結(jié)構(gòu)型模式以及行為型模式。這里不再對設(shè)計模式進行展開說明,后面有時間可以寫系列文章專門進行介紹。不過我們需要清楚的是這23種設(shè)計模式就是程序員寫代碼打天下的招式,而提升代碼擴展性才是最終目的。
圖片
面向失敗編碼
代碼中的異常處理往往最能體現(xiàn)技術(shù)同學(xué)的編碼功力。完成一個需求并不難,但是能夠考慮到各種異常情況,在異常發(fā)生的時候依然可以得到預(yù)想輸出的代碼,卻不是每個程序員都能寫出來的。 因此無論是寫代碼還是系統(tǒng)設(shè)計,都要有面向失敗進行設(shè)計的意識,每一個業(yè)務(wù)流程都要考慮如果失敗了應(yīng)該怎么辦,盡可能考慮周全可能會出現(xiàn)的意外情況,同時針對這些意外情況設(shè)計相應(yīng)的兜底措施,以實現(xiàn)防御性編碼。
這里假設(shè)有這樣的業(yè)務(wù)場景,當(dāng)我們的業(yè)務(wù)中有調(diào)用外部服務(wù)接口的邏輯,那么我們在編寫這部分代碼的時候就需要考慮面向失敗進行編碼。因為調(diào)用外部接口有可能成功,有可能失敗。如果接口調(diào)用成功自然沒什么好說的,繼續(xù)執(zhí)行后續(xù)的業(yè)務(wù)邏輯就好。但是如果調(diào)用失敗了怎么辦,是直接將調(diào)用異常返回還是進行重試,如果重試還是失敗應(yīng)該怎么辦,需不需要設(shè)計下重試的策略,比如連續(xù)重試三次都失敗的話,后續(xù)間隔固定時間再進行重試等等。當(dāng)然我們并不需要在每個這樣的業(yè)務(wù)流程中這么做,在一些比較核心的業(yè)務(wù)鏈路中不能出錯的流程中要有兜底措施。
圖片
總結(jié)
本文主要從理論層面為大家介紹寫好代碼的需要哪些知識儲備,下一篇會從具體業(yè)務(wù)場景出發(fā),具體實操怎么結(jié)合這些理論知識來把代碼寫好。不過我們必須認(rèn)識到好代碼是需要不斷打磨的,并非一朝一夕就能練就,總是需要在不斷的實踐,不斷的思考,不斷的體會以及不斷的沉淀中實現(xiàn)代碼能力的提升。左手設(shè)計原則,右手設(shè)計模式,心中領(lǐng)域模型再加上強烈的內(nèi)在驅(qū)動,我相信我們有信心一定可以寫出一手好代碼。