什么是優(yōu)雅的代碼設(shè)計(jì)
今天我來(lái)解釋一下什么樣的代碼才是優(yōu)雅的代碼設(shè)計(jì)。當(dāng)然我們的代碼根據(jù)實(shí)際的應(yīng)用場(chǎng)景也分了很多維度,有偏向于底層系統(tǒng)的,有偏向于中間件的,也有偏向上層業(yè)務(wù)的,還有偏向于前端展示的。今天我主要來(lái)跟大家分析一下我對(duì)于業(yè)務(wù)代碼的理解以及什么樣才是我認(rèn)為的優(yōu)雅的業(yè)務(wù)代碼設(shè)計(jì)。
大家吐槽非常多的是,我們這邊的業(yè)務(wù)代碼會(huì)存在著大量的不斷地持續(xù)的變化,導(dǎo)致我們的程序員對(duì)于業(yè)務(wù)代碼設(shè)計(jì)得就比較隨意。往往為了快速上線隨意堆疊,不加深入思考,或者是怕影響到原來(lái)的流程,而不斷在原來(lái)的代碼上增加分支流程。
這種思想進(jìn)一步使得代碼腐化,使得大量的程序員更失去了“好代碼”的標(biāo)準(zhǔn)。
那么如果代碼優(yōu)雅,那么要有哪些特征呢?或者說(shuō)我們做哪些事情才會(huì)使得代碼變得更加優(yōu)雅呢?
結(jié)構(gòu)化
結(jié)構(gòu)化定義是指對(duì)某一概念或事物進(jìn)行系統(tǒng)化、規(guī)范化的分析和定義,包括定義的范圍、對(duì)象的屬性、關(guān)系等方面,旨在準(zhǔn)確地描述和定義所要表達(dá)的概念或事物。
我覺(jué)得首要的是代碼,要一個(gè)骨架。就跟我們所說(shuō)的思維結(jié)構(gòu)是一樣,我們對(duì)一個(gè)事物的判斷,一般都是綜合、立體和全面的,否則就會(huì)成為了盲人摸象,只見(jiàn)一斑。因此對(duì)于一個(gè)事物的判斷,要綜合、結(jié)構(gòu)和全面。對(duì)于一段代碼來(lái)說(shuō)也是一樣的標(biāo)準(zhǔn),首先就是結(jié)構(gòu)化。結(jié)構(gòu)化是對(duì)一段代碼最基本的要求,一個(gè)有良好結(jié)構(gòu)的代碼才可能稱得上是好代碼,如果只是想到哪里就寫(xiě)到哪里,一定成不了最優(yōu)質(zhì)的代碼。
代碼的結(jié)構(gòu)化,能夠讓維護(hù)的人一眼就能看出主次結(jié)構(gòu)、看出分層結(jié)構(gòu),能夠快速掌握一段代碼或者一段模塊要完成的核心事情。
精簡(jiǎn)
代碼跟我們抽象現(xiàn)實(shí)的物體一樣,也要非常地精簡(jiǎn)。其實(shí)精簡(jiǎn)我覺(jué)得不僅在代碼,在所有藝術(shù)品里面都是一樣的,包括電影。電影雖然可能長(zhǎng)達(dá)一個(gè)小時(shí),兩個(gè)小時(shí),但你會(huì)發(fā)現(xiàn)優(yōu)雅的電影它沒(méi)有一幀是多余的,每出現(xiàn)的一個(gè)畫(huà)面、一個(gè)細(xì)節(jié),都是電影里要表達(dá)的某個(gè)情緒有關(guān)聯(lián)。我們所說(shuō)的文章也是一樣,沒(méi)有任何一個(gè)伏筆是多余的。代碼也是一樣,嚴(yán)格來(lái)說(shuō)代碼沒(méi)有一個(gè)字符、函數(shù)、變量是多余的,每個(gè)代碼都有它應(yīng)該有的用處。就跟“奧卡姆剃刀”原理一樣,每塊代碼都有它存在的價(jià)值包括注釋。
但正如我們的創(chuàng)作一樣,要完成一個(gè)功能,我們把代碼寫(xiě)得復(fù)雜是簡(jiǎn)單的,但我們把它寫(xiě)得簡(jiǎn)單是非常難的。代碼是思維結(jié)構(gòu)的一種體現(xiàn),而往往抽象能力是最為關(guān)鍵的,也是最難的。合適的抽象以及合理的抽象才能夠讓代碼濃縮到最少的代碼函數(shù)。
大部分情況來(lái)說(shuō),代碼行數(shù)越少,則運(yùn)行效率會(huì)越高。當(dāng)然也不要成為極端的反面例子,不要一味追求極度少量的代碼。代碼的優(yōu)雅一定是精要的,該有的有,不該有的一定是沒(méi)有的。所以在完成一個(gè)業(yè)務(wù)邏輯的時(shí)候,一定要多問(wèn)自己這個(gè)代碼是不是必須有的,能不能以一種簡(jiǎn)要的方式來(lái)表達(dá)。
善用最佳實(shí)踐
俗話說(shuō)太陽(yáng)底下沒(méi)有新鮮事兒,一般來(lái)說(shuō),沒(méi)有一個(gè)業(yè)務(wù)場(chǎng)景所需要用到的編碼方式是需要你獨(dú)創(chuàng)發(fā)明的。你所寫(xiě)的代碼功能大概率都有人遇到過(guò),因此對(duì)于大部分常用的編碼模式,也都大家被抽象出來(lái)了一些最佳實(shí)踐。那么最經(jīng)典的就是23種設(shè)計(jì)模式,基本上可以涵蓋90%以上的業(yè)務(wù)場(chǎng)景了。
以下是23種設(shè)計(jì)模式的部分簡(jiǎn)單介紹:
單例模式(Singleton?Pattern):確保類只有一個(gè)實(shí)例,并提供全局訪問(wèn)點(diǎn)。
工廠模式(Factory?Pattern):定義一個(gè)用于創(chuàng)建對(duì)象的接口,并讓子類決定實(shí)例化哪個(gè)對(duì)象。
模板方法模式(Template?Method?Pattern):提供一種動(dòng)態(tài)的創(chuàng)建對(duì)象的方法,通過(guò)使用不同的模板來(lái)創(chuàng)建對(duì)象。
裝飾器模式(Decorator?Pattern):將對(duì)象包裝成另一個(gè)對(duì)象,從而改變?cè)袑?duì)象的行為。
適配器模式(Adapter?Pattern):將一個(gè)類的接口轉(zhuǎn)換成客戶希望的另一個(gè)接口,以使其能夠與不同的對(duì)象交互。
外觀模式(Facade?Pattern):將對(duì)象的不同方面組合成一個(gè)單一的接口,從而使客戶端只需訪問(wèn)該接口即可使用整個(gè)對(duì)象。
我們所說(shuō)的設(shè)計(jì)模式就是一種對(duì)常用代碼結(jié)構(gòu)的一種抽象或者說(shuō)套路。并不是說(shuō)我們一定要用設(shè)計(jì)模式來(lái)實(shí)現(xiàn)功能,而是說(shuō)我們要有一種最高效,最通常的方式去實(shí)現(xiàn)。這種方式帶來(lái)了好處就是高效,而且別人理解起來(lái)也相對(duì)來(lái)說(shuō)比較容易。
我們也不大推薦對(duì)于一些常見(jiàn)功能用一些花里胡哨的方式來(lái)實(shí)現(xiàn),這樣往往可能導(dǎo)致過(guò)度設(shè)計(jì),但實(shí)際用處可能反而會(huì)帶來(lái)其他問(wèn)題。我覺(jué)得要用一些新型的代碼,新型的思維方式應(yīng)該是在一些比較新的場(chǎng)景里面去使用,去驗(yàn)證,而不應(yīng)該在我們已有最佳實(shí)踐的方式上去造額外的輪子。
這個(gè)就比如我們?nèi)绻O(shè)計(jì)一輛汽車,我們應(yīng)該采用當(dāng)前最新最成熟的發(fā)動(dòng)機(jī)方案,而不應(yīng)該從零開(kāi)始自己再造一套新的發(fā)動(dòng)機(jī)。但是如果這個(gè)發(fā)動(dòng)機(jī)是在土星使用,要面對(duì)極端的環(huán)境,可能就需要基于當(dāng)前的方案研制一套全新的發(fā)動(dòng)機(jī)系統(tǒng),但是大部分人是沒(méi)有機(jī)會(huì)碰到土星這種業(yè)務(wù)環(huán)境的。所以通常情況下,還是不要在不需要?jiǎng)?chuàng)新的地方去創(chuàng)新。
除了善用最佳實(shí)踐模式之快,我們還應(yīng)該采用更高層的一些最佳實(shí)踐框架的解決方案。比如我們?cè)诿鎸?duì)非常抽象,非常靈活變動(dòng)的一些規(guī)則的管理上,我們可以使用大量的規(guī)則引擎工具。比如針對(duì)于流程式的業(yè)務(wù)模型上面,我們可以引入一些工作流的引擎。在需要RPC框架的時(shí)候,我們可以根據(jù)業(yè)務(wù)情況去調(diào)研使用HTTP還是DUBBO,可以集百家之所長(zhǎng)。
持續(xù)重構(gòu)
好代碼往往不是一蹴而就的,而是需要我們持續(xù)打磨。有很多時(shí)候由于業(yè)務(wù)的變化以及我們思維的局限性,我們沒(méi)有辦法一次性就能夠設(shè)計(jì)出最優(yōu)的代碼質(zhì)量,往往需要我們后續(xù)持續(xù)的優(yōu)化。所以除了初始化的設(shè)計(jì)以外,我們還應(yīng)該在業(yè)務(wù)持續(xù)的發(fā)展過(guò)程中動(dòng)態(tài)地去對(duì)代碼進(jìn)行重構(gòu)。
但是往往程序員由于業(yè)務(wù)繁忙或者自身的懶惰,在業(yè)務(wù)代碼上線正常運(yùn)行后,就打死不愿意再動(dòng)原來(lái)的代碼。第一個(gè)是覺(jué)得跑得沒(méi)有問(wèn)題了何必去改,第二個(gè)就是改動(dòng)了反而可能引起故障。這就是一種完全錯(cuò)誤的思維,一來(lái)是給自己寫(xiě)不好的線上代碼的一個(gè)借口,二來(lái)是沒(méi)有讓自己持續(xù)進(jìn)步的機(jī)會(huì)。
代碼重構(gòu)的原則有很多,這里我就不再細(xì)講。但是始終我覺(jué)得對(duì)線上第一個(gè)要敬畏,第二個(gè)也要花時(shí)間持續(xù)續(xù)治理。往往我們?cè)诤芏鄷r(shí)候初始化的架構(gòu)是比較優(yōu)雅的,是經(jīng)過(guò)充分設(shè)計(jì)的,但是也是由于業(yè)務(wù)發(fā)展的迭代的原因,我們持續(xù)在存量代碼上添加新功能。
有時(shí)候有一些不同的同學(xué)水平不一樣,能力也不一樣,所以導(dǎo)致后面寫(xiě)上的代碼會(huì)非常地隨意,導(dǎo)致整個(gè)系統(tǒng)就會(huì)變得越來(lái)越累贅,到了最后就不敢有新同學(xué)上去改,或者是稍微一改可能就引起未知的故障。
所以在這種情況下,如果還在追求優(yōu)質(zhì)的代碼,就需要持續(xù)不斷地重構(gòu)。重構(gòu)需要持續(xù)改善,并且最好每次借業(yè)務(wù)變更時(shí),做小幅度的修改以降低風(fēng)險(xiǎn)。長(zhǎng)此以往,整體的代碼結(jié)構(gòu)就得以大幅度的修改,真正達(dá)到集腋成裘的目的。下面是一些常見(jiàn)的重構(gòu)原則:
單一職責(zé)原則:每個(gè)類或模塊應(yīng)該只負(fù)責(zé)一個(gè)單一的任務(wù)。這有助于降低代碼的復(fù)雜度和維護(hù)成本。
開(kāi)閉原則:軟件實(shí)體(類、模塊等)應(yīng)該對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。這樣可以保證代碼的靈活性和可維護(hù)性。
里氏替換原則:任何基類都可以被其子類替換。這可以減少代碼的耦合度,提高代碼的可擴(kuò)展性。
接口隔離原則:不同的接口應(yīng)該是相互獨(dú)立的,它們只依賴于自己需要的實(shí)現(xiàn),而不是其他接口。
依賴倒置原則:高層模塊不應(yīng)該依賴低層模塊,而是依賴應(yīng)用程序的功能。這可以降低代碼的復(fù)雜度和耦合度。
高內(nèi)聚低耦合原則:盡可能使模塊內(nèi)部的耦合度低,而模塊之間的耦合度高。這可以提高代碼的可維護(hù)性和可擴(kuò)展性。
抽象工廠原則:使用抽象工廠來(lái)創(chuàng)建對(duì)象,這樣可以減少代碼的復(fù)雜度和耦合度。
單一視圖原則:每個(gè)頁(yè)面只應(yīng)該有一個(gè)視圖,這可以提高代碼的可讀性和可維護(hù)性。
依賴追蹤原則:對(duì)代碼中的所有依賴關(guān)系進(jìn)行跟蹤,并在必要時(shí)進(jìn)行修復(fù)或重構(gòu)。
測(cè)試驅(qū)動(dòng)開(kāi)發(fā)原則:在編寫(xiě)代碼之前編寫(xiě)測(cè)試用例,并在開(kāi)發(fā)過(guò)程中持續(xù)編寫(xiě)和運(yùn)行測(cè)試用例,以確保代碼的質(zhì)量和穩(wěn)定性。
綜合
綜上所述,代碼要有結(jié)構(gòu)化、可擴(kuò)展、用最佳實(shí)踐和持續(xù)重構(gòu)。追求卓越的優(yōu)質(zhì)代碼應(yīng)該是每一位工程師的基本追求和基本要求,只有這樣,才能不斷地使得自己成為一名卓越的工程師。