編寫(xiě)優(yōu)雅代碼

2018-02-24 15:46 更新

原文出處:http://weibo.com/p/1001643877361430185536
作者:秦迪,@蛋疼的AXB

document/2015-09-14/55f667c23be38

課程大綱

什么是好代碼

對(duì)于代碼質(zhì)量的定義需要于從兩個(gè)維度分析:主觀的,被人類(lèi)理解的部分;還有客觀的,在計(jì)算機(jī)里運(yùn)行的狀況。

我把代碼質(zhì)量分為五個(gè)層次,依次為:

  • 完成功能的代碼

  • 高性能的代碼

  • 易讀的代碼

  • 可測(cè)試的代碼

  • 可擴(kuò)展的代碼

如何編寫(xiě)可讀的代碼

在很多跟代碼質(zhì)量有關(guān)的書(shū)里都強(qiáng)調(diào)了一個(gè)觀點(diǎn):程序首先是給人看的,其次才是能被機(jī)器執(zhí)行。

逐字翻譯

在評(píng)價(jià)一段代碼能不能讓人看懂的時(shí)候,可以自己把這段代碼逐字翻譯成中文,試著組成句子,之后把中文句子讀給另一個(gè)人沒(méi)有看過(guò)這段代碼的人聽(tīng),如果另一個(gè)人能聽(tīng)懂,那么這段代碼的可讀性基本就合格了。

而實(shí)際閱讀代碼時(shí),讀者也會(huì)一個(gè)詞一個(gè)詞的閱讀,推斷這句話(huà)的意思,如果僅靠句子無(wú)法理解,那么就需要聯(lián)系上下文理解這句代碼,如果簡(jiǎn)單的聯(lián)系上下文也理解不了,可能還要掌握更多其它部分的細(xì)節(jié)來(lái)幫助推斷。大部分情況下,理解一句代碼在做什么需要聯(lián)系的上下文越多,意味著代碼的質(zhì)量越差。

逐字翻譯的好處是能讓作者能輕易的發(fā)現(xiàn)那些只有自己知道的、沒(méi)有體現(xiàn)在代碼里的假設(shè)和可讀性陷阱。無(wú)法從字面意義上翻譯出原本意思的代碼大多都是爛代碼,比如“ms代表messageService“,或者“ms.proc()是發(fā)消息“,或者“tmp代表當(dāng)前的文件”。

遵循約定

約定包括代碼和文檔如何組織,注釋如何編寫(xiě),編碼風(fēng)格的約定等等,這對(duì)于代碼未來(lái)的維護(hù)很重要。

大家剛開(kāi)始工作時(shí),一般需要與部門(mén)的約定保持一致,包括一些強(qiáng)制的規(guī)定,如代碼的格式化設(shè)置文件;或者一些非強(qiáng)制的約定,如工程的命名等。

從更大的范圍考慮,整個(gè)行業(yè)的約定和規(guī)則也在不斷的更新。所以在工作中也要對(duì)行業(yè)動(dòng)向和開(kāi)源項(xiàng)目保持關(guān)注,如果發(fā)現(xiàn)部門(mén)中哪一項(xiàng)約定已經(jīng)過(guò)時(shí)了,那么可以隨時(shí)提出來(lái),由平臺(tái)的架構(gòu)師小組review之后推進(jìn)平臺(tái)更新這些規(guī)則。

文檔和注釋

對(duì)于文檔的標(biāo)準(zhǔn)很簡(jiǎn)單,能找到、能讀懂就可以了,一般一個(gè)工程至少要包含以下幾類(lèi)文檔:

  1. 對(duì)于項(xiàng)目的介紹,包括項(xiàng)目功能、作者、目錄結(jié)構(gòu)等,讀者應(yīng)該能3分鐘內(nèi)大致理解這個(gè)工程是做什么的。

  2. 針對(duì)新人的QuickStart,讀者按照文檔說(shuō)明應(yīng)該能在1小時(shí)內(nèi)完成代碼構(gòu)建和簡(jiǎn)單使用。

  3. 針對(duì)使用者的詳細(xì)說(shuō)明文檔,比如接口定義、參數(shù)含義、設(shè)計(jì)等,讀者能通過(guò)文檔了解這些功能(或接口)的使用方法。

有一部分注釋實(shí)際是文檔,比如javadoc。這樣能把源碼和注釋放在一起,對(duì)于讀者更清晰,也能簡(jiǎn)化不少文檔的維護(hù)的工作。

還有一類(lèi)注釋并不作為文檔的一部分,比如函數(shù)內(nèi)部的注釋?zhuān)@類(lèi)注釋的職責(zé)是說(shuō)明一些代碼本身無(wú)法表達(dá)的作者在編碼時(shí)的思考,比如“為什么這里沒(méi)有做XX”,或者“這里要注意XX問(wèn)題”。

一般來(lái)說(shuō)函數(shù)內(nèi)部注釋的數(shù)量應(yīng)該不會(huì)有很多,也不會(huì)完全沒(méi)有,一般滾動(dòng)幾屏幕看到一兩處左右比較正常。過(guò)多的話(huà)可能意味著代碼本身的可讀性有問(wèn)題,而如果一點(diǎn)都沒(méi)有可能意味著有些隱藏的邏輯沒(méi)有說(shuō)明,需要考慮適當(dāng)?shù)脑黾右稽c(diǎn)注釋了。

其次也需要考慮注釋的質(zhì)量:在代碼可讀性合格的基礎(chǔ)上,注釋?xiě)?yīng)該提供比代碼更多的信息。文檔和注釋并不是越多越好,它們可能會(huì)導(dǎo)致維護(hù)成本增加。

如何編寫(xiě)可發(fā)布的代碼

剛開(kāi)始接觸高并發(fā)線上系統(tǒng)的新同學(xué)經(jīng)常容易出現(xiàn)一個(gè)問(wèn)題:寫(xiě)的代碼在發(fā)布之后才發(fā)現(xiàn)很多考慮不到的地方,比如說(shuō)測(cè)試的時(shí)候沒(méi)問(wèn)題,項(xiàng)目發(fā)布之后發(fā)現(xiàn)有很多意料之外的狀況;或者出了問(wèn)題之后不知道從哪下手排查,等等。

這里介紹一下從代碼編寫(xiě)完成到發(fā)布前需要注意的一些地方。

處理異常

新手程序員普遍沒(méi)有處理異常的意識(shí),但代碼的實(shí)際運(yùn)行環(huán)境中充滿(mǎn)了異常:服務(wù)器會(huì)死機(jī),網(wǎng)絡(luò)會(huì)超時(shí),用戶(hù)會(huì)胡亂操作,不懷好意的人會(huì)惡意攻擊你的系統(tǒng)。

對(duì)一段代碼異常處理能力的第一印象應(yīng)該來(lái)自于單元測(cè)試的覆蓋率。大部分異常難以在開(kāi)發(fā)或者測(cè)試環(huán)境里復(fù)現(xiàn),依靠測(cè)試團(tuán)隊(duì)也很難在集成測(cè)試環(huán)境中模擬所有的異常情況。

而單元測(cè)試可以比較簡(jiǎn)單的模擬各種異常情況,如果一個(gè)模塊的單元測(cè)試覆蓋率連50%都不到,很難想象這些代碼考慮了異常情況下的處理,即使考慮了,這些異常處理的分支都沒(méi)有被驗(yàn)證過(guò),怎么指望實(shí)際運(yùn)行環(huán)境中出現(xiàn)問(wèn)題時(shí)表現(xiàn)良好呢?

處理并發(fā)

而是否高質(zhì)量的實(shí)現(xiàn)并發(fā)編程的關(guān)鍵并不是是否應(yīng)用了某種同步策略,而是看代碼中是否保護(hù)了共享資源:

  • 局部變量之外的內(nèi)存訪問(wèn)都有并發(fā)風(fēng)險(xiǎn)(比如訪問(wèn)對(duì)象的屬性,訪問(wèn)靜態(tài)變量等)

  • 訪問(wèn)共享資源也會(huì)有并發(fā)風(fēng)險(xiǎn)(比如緩存、數(shù)據(jù)庫(kù)等)。

  • 被調(diào)用方如果不是聲明為線程安全的,那么很有可能存在并發(fā)問(wèn)題(比如java的hashmap)。

  • 所有依賴(lài)時(shí)序的操作,即使每一步操作都是線程安全的,還是存在并發(fā)問(wèn)題(比如先刪除一條記錄,然后把記錄數(shù)減一)。

前三種情況能夠比較簡(jiǎn)單的通過(guò)代碼本身分辨出來(lái),只要簡(jiǎn)單培養(yǎng)一下自己對(duì)于共享資源調(diào)用的敏感度就可以了。

但是對(duì)于最后一種情況,往往很難簡(jiǎn)單的通過(guò)看代碼的方式看出來(lái),甚至出現(xiàn)并發(fā)問(wèn)題的兩處調(diào)用并不是在同一個(gè)程序里(比如兩個(gè)系統(tǒng)同時(shí)讀寫(xiě)一個(gè)數(shù)據(jù)庫(kù),或者并發(fā)的調(diào)用了一個(gè)程序的不同模塊等)。但是,只要是代碼里出現(xiàn)了不加鎖的,訪問(wèn)共享資源的“先做A,再做B”之類(lèi)的邏輯,可能就需要提高警惕了。

優(yōu)化性能

性能是評(píng)價(jià)程序員能力的一個(gè)重要指標(biāo),但要評(píng)價(jià)程序的性能往往要借助于一些性能測(cè)試工具,或者在實(shí)際環(huán)境中執(zhí)行才能有結(jié)果。

如果僅從代碼的角度考慮,有兩個(gè)評(píng)價(jià)執(zhí)行效率的辦法:

  • 算法的時(shí)間復(fù)雜度,時(shí)間復(fù)雜度高的程序運(yùn)行效率必然會(huì)低。

  • 單步操作耗時(shí),單步耗時(shí)高的操作盡量少做,比如訪問(wèn)數(shù)據(jù)庫(kù),訪問(wèn)io等,這里需要建立對(duì)各種操作的耗時(shí)的概念。

而實(shí)際工作中,也會(huì)見(jiàn)到一些同學(xué)過(guò)于熱衷?xún)?yōu)化效率,相對(duì)的會(huì)帶來(lái)程序易讀性的降低、復(fù)雜度提高、或者增加工期等等。所以在優(yōu)化之前最好多想想這段程序的瓶頸在哪里,為什么會(huì)有這個(gè)瓶頸,以及優(yōu)化帶來(lái)的收益。

再?gòu)?qiáng)調(diào)一下,無(wú)論是優(yōu)化不足還是優(yōu)化過(guò)度,判斷性能指標(biāo)最好的辦法是用數(shù)據(jù)說(shuō)話(huà),而不是單純看代碼。

日志

日志代表了程序在出現(xiàn)問(wèn)題時(shí)排查的難易程度,對(duì)于日志的評(píng)價(jià)標(biāo)準(zhǔn)有三個(gè):

  • 日志是否足夠,所有異常、外部調(diào)用都需要有日志,而一條調(diào)用鏈路上的入口、出口和路徑關(guān)鍵點(diǎn)上也需要有日志。

  • 日志的表達(dá)是否清晰,包括是否能讀懂,風(fēng)格是否統(tǒng)一等。這個(gè)的評(píng)價(jià)標(biāo)準(zhǔn)跟代碼的可讀性一樣,不重復(fù)了。

  • 日志是否包含了足夠的信息,這里包括了調(diào)用的上下文、外部的返回值,用于查詢(xún)的關(guān)鍵字等,便于分析信息。

對(duì)于線上系統(tǒng)來(lái)說(shuō),一般可以通過(guò)調(diào)整日志級(jí)別來(lái)控制日志的數(shù)量,所以打印日志的代碼只要不對(duì)閱讀造成障礙,基本上都是可以接受的。

如何編寫(xiě)可維護(hù)的代碼

可維護(hù)性要對(duì)應(yīng)的是未來(lái)的情況,但是一般新人很難想象現(xiàn)在的一些做法會(huì)對(duì)未來(lái)造成什么影響。

避免重復(fù)

代碼重復(fù)分為兩種:模塊內(nèi)重復(fù)和模塊間重復(fù)。兩種重復(fù)都在一定程度上說(shuō)明了編碼的水平有問(wèn)題。現(xiàn)代的IDE都提供了檢查重復(fù)代碼的工具,只需點(diǎn)幾下鼠標(biāo)就可以判斷了。

除了代碼重復(fù)之外,還會(huì)出現(xiàn)另一類(lèi)重復(fù):信息重復(fù)。

比方說(shuō)每行代碼前面都寫(xiě)一句注釋?zhuān)欢螘r(shí)間之后維護(hù)的同學(xué)忘了更新注釋?zhuān)瑢?dǎo)致注釋的內(nèi)容和代碼本身變得不一致了。此時(shí)過(guò)多的注釋反而成了雞肋,看之無(wú)用,刪之可惜。

隨著項(xiàng)目的演進(jìn),無(wú)用的信息會(huì)越積越多,最終甚至讓人無(wú)法分辨哪些信息是有效的,哪些是無(wú)效的。

如果在項(xiàng)目中發(fā)現(xiàn)好幾個(gè)東西都在做同一件事情,比如通過(guò)注釋描述代碼在做什么,或者依靠注釋替代版本管理的功能,這些都是需要避免的。

劃分模塊

模塊內(nèi)高內(nèi)聚與模塊間低耦合是大部分設(shè)計(jì)遵循的標(biāo)準(zhǔn),通過(guò)合理的模塊劃分能夠把復(fù)雜的功能拆分為更易于維護(hù)的更小的功能點(diǎn)。

一般來(lái)說(shuō)可以從代碼長(zhǎng)度上初步評(píng)價(jià)一個(gè)模塊劃分的是否合理,一個(gè)類(lèi)的長(zhǎng)度大于2000行,或者一個(gè)函數(shù)的長(zhǎng)度大于兩屏幕都是比較危險(xiǎn)的信號(hào)。

另一個(gè)能夠體現(xiàn)模塊劃分水平的地方是依賴(lài)。如果一個(gè)模塊依賴(lài)特別多,甚至出現(xiàn)了循環(huán)依賴(lài),那么也可以反映出作者對(duì)模塊的規(guī)劃比較差,今后在維護(hù)這個(gè)工程的時(shí)候很有可能出現(xiàn)牽一發(fā)而動(dòng)全身的情況。

有不少工具能提供依賴(lài)分析,比如IDEA中提供的Dependencies Analysis功能,學(xué)會(huì)這些工具的使用對(duì)于評(píng)價(jià)代碼質(zhì)量會(huì)有很大的幫助。

值得一提的是,絕大部分情況下,不恰當(dāng)?shù)哪K劃分也會(huì)伴隨著極低的單元測(cè)試覆蓋率:復(fù)雜模塊的單元測(cè)試非常難寫(xiě)的,甚至是不可能完成的任務(wù)。

簡(jiǎn)潔與抽象

只要提到代碼質(zhì)量,必然會(huì)提到簡(jiǎn)潔、優(yōu)雅之類(lèi)的形容詞。簡(jiǎn)潔這個(gè)詞實(shí)際涵蓋了很多東西,代碼避免重復(fù)是簡(jiǎn)潔、設(shè)計(jì)足夠抽象是簡(jiǎn)潔,一切對(duì)于提高可維護(hù)性的嘗試實(shí)際都是在試圖做減法。

編程經(jīng)驗(yàn)不足的程序員往往不能意識(shí)到簡(jiǎn)潔的重要性,樂(lè)于搗鼓一些復(fù)雜的玩意并樂(lè)此不疲。但復(fù)雜是代碼可維護(hù)性的天敵,也是程序員能力的一道門(mén)檻。

跨過(guò)門(mén)檻的程序員應(yīng)該有能力控制逐漸增長(zhǎng)的復(fù)雜度,總結(jié)和抽象出事物的本質(zhì),并體現(xiàn)到自己設(shè)計(jì)和編碼中。一個(gè)程序的生命周期也是在由簡(jiǎn)入繁到化繁為簡(jiǎn)中不斷迭代的過(guò)程。

簡(jiǎn)潔與抽象更像是一種思維方式,除了要理解、還需要練習(xí)。多看、多想、多交流,很多時(shí)候可以簡(jiǎn)化的東西會(huì)大大超出原先的預(yù)計(jì)。

如何做出優(yōu)雅的設(shè)計(jì)

當(dāng)程序的功能越來(lái)越多時(shí),編程就不再只是寫(xiě)代碼,而會(huì)涉及到模塊的劃分、和模塊之間的交互等內(nèi)容。對(duì)于新同學(xué)來(lái)說(shuō),一開(kāi)始很難寫(xiě)出優(yōu)雅的設(shè)計(jì)。

這一節(jié)會(huì)討論一下如何能讓自己編寫(xiě)的代碼有更強(qiáng)的“設(shè)計(jì)感”。

參考設(shè)計(jì)模式

最容易快速上手的提升自己代碼設(shè)計(jì)水平的方式就是參考其他人的設(shè)計(jì),這些前人總結(jié)的面對(duì)常見(jiàn)場(chǎng)景時(shí)如何進(jìn)行模塊劃分和交互的方式被稱(chēng)作設(shè)計(jì)模式。

設(shè)計(jì)模式的分類(lèi)

  • 創(chuàng)建型模式主要用于創(chuàng)建對(duì)象。

  • 結(jié)構(gòu)型模式主要用于處理類(lèi)或?qū)ο蟮慕M合。

  • 行為型模式主要用于描述對(duì)類(lèi)或?qū)ο笤鯓咏换ズ驮鯓臃峙渎氊?zé)。

由于篇幅有限,這里不再展開(kāi)每一種設(shè)計(jì)模式的用途。這部分資料和書(shū)籍已經(jīng)比較全了,可以課下學(xué)習(xí)。

編寫(xiě)單元測(cè)試

單元測(cè)試是什么

維基百科上的詞條說(shuō)明:

單元測(cè)試是針對(duì)程序模塊(軟件設(shè)計(jì)的最小單位)來(lái)進(jìn)行正確性檢驗(yàn)的測(cè)試工作。程序單元是應(yīng)用的最小可測(cè)試部件。在過(guò)程化編程中,一個(gè)單元就是單個(gè)程序、函數(shù)、過(guò)程等;對(duì)于面向?qū)ο缶幊?,最小單元就是方法,包括基?lèi)(超類(lèi))、抽象類(lèi)、或者派生類(lèi)(子類(lèi))中的方法。

所以,我眼中的“合格的”單元測(cè)試需要滿(mǎn)足幾個(gè)條件:

  1. 測(cè)試的是一個(gè)代碼單元內(nèi)部的邏輯,而不是各模塊之間的交互。

  2. 無(wú)依賴(lài),不需要實(shí)際運(yùn)行環(huán)境就可以測(cè)試代碼。

  3. 運(yùn)行效率高,可以隨時(shí)執(zhí)行。

單元測(cè)試的目的

了解了單元測(cè)試是什么之后,第二個(gè)問(wèn)題就是:?jiǎn)卧獪y(cè)試是用來(lái)做什么的?

很多人第一反應(yīng)是“看看程序有沒(méi)有問(wèn)題”。但是,只是使用單元測(cè)試來(lái)“看看程序有沒(méi)有問(wèn)題”的話(huà),效率反而不如把程序運(yùn)行起來(lái)直接查看結(jié)果。原因有兩個(gè):

  1. 單元測(cè)試要寫(xiě)額外的代碼,而不寫(xiě)單元測(cè)試,直接運(yùn)行程序也可以測(cè)試程序有沒(méi)有問(wèn)題。

  2. 即使通過(guò)了單元測(cè)試,程序在實(shí)際運(yùn)行的時(shí)候仍然有可能出問(wèn)題。

在這里我總結(jié)了一下幾個(gè)比較常見(jiàn)的單元測(cè)試的幾個(gè)典型場(chǎng)景:

  1. 開(kāi)發(fā)前寫(xiě)單元測(cè)試,通過(guò)測(cè)試描述需求,由測(cè)試驅(qū)動(dòng)開(kāi)發(fā)。

  2. 在開(kāi)發(fā)過(guò)程中及時(shí)得到反饋,提前發(fā)現(xiàn)問(wèn)題。

  3. 應(yīng)用于自動(dòng)化構(gòu)建或持續(xù)集成流程,對(duì)每次代碼修改做回歸測(cè)試。

  4. 作為重構(gòu)的基礎(chǔ),驗(yàn)證重構(gòu)是否可靠。

還有最重要的一點(diǎn):編寫(xiě)單元測(cè)試的難度和代碼設(shè)計(jì)的好壞息息相關(guān),單元測(cè)試測(cè)的三分是代碼,七分是設(shè)計(jì),能寫(xiě)出單元測(cè)試的代碼基本上可以說(shuō)明這段代碼的設(shè)計(jì)是比較合理的。能寫(xiě)出和寫(xiě)不出單元測(cè)試之間體現(xiàn)了編程能力上的巨大的鴻溝,無(wú)論是什么樣的程序員,堅(jiān)持編寫(xiě)一段時(shí)間的單元測(cè)試之后,都會(huì)明顯感受到代碼設(shè)計(jì)能力的巨大提升。

如何編寫(xiě)單元測(cè)試

測(cè)試代碼不像普通的應(yīng)用程序一樣有著很明確的作為“值”的輸入和輸出。舉個(gè)例子,假如一個(gè)普通的函數(shù)要做下面這件事情:

  • 接收一個(gè)user對(duì)象作為參數(shù)

  • 調(diào)用dao層的update方法更新用戶(hù)屬性

  • 返回true/false結(jié)果

那么,只需要在函數(shù)中聲明一個(gè)參數(shù)、做一次調(diào)用、返回一個(gè)布爾值就可以了。但如果要對(duì)這個(gè)函數(shù)做一個(gè)“純粹的”單元測(cè)試,那么它的輸入和輸出會(huì)有很多情況,比如其中一個(gè)測(cè)試是這樣:

  • 假設(shè)調(diào)用dao層的update方法會(huì)返回true。

  • 程序去調(diào)用service層的update方法。

  • 驗(yàn)證一下service是不是也返回了true。

而具體的測(cè)試內(nèi)容可以依賴(lài)單元測(cè)試框架提供的功能來(lái)完成。

單元測(cè)試框架

運(yùn)行框架:

  • jUnit

  • TestNG

  • Spock

Mock框架:

  • Mockito

  • EasyMock

  • PowerMock

  • Spock

由于篇幅限制,這里不再展開(kāi)具體的框架用法了,有興趣的同學(xué)可以自行搜索相關(guān)文章。

如何規(guī)劃合理的架構(gòu)

很多新同學(xué)的規(guī)劃都是未來(lái)成為架構(gòu)師,要做架構(gòu)師就免不了設(shè)計(jì)架構(gòu)。而在微博平臺(tái)工作也會(huì)經(jīng)常跟架構(gòu)打交道,由于后面有獨(dú)立的架構(gòu)課程,這里只是簡(jiǎn)單介紹一下常見(jiàn)的架構(gòu)模式。

常見(jiàn)的架構(gòu)模式

分層架構(gòu)

分層架構(gòu)是應(yīng)用很普遍架構(gòu)模式,它能降低模塊之間的耦合,便于測(cè)試開(kāi)發(fā),它也是程序員需要掌握的基礎(chǔ)。

典型的分層架構(gòu)模式如下:

上圖中是一個(gè)4層的架構(gòu),在現(xiàn)實(shí)場(chǎng)景中,層數(shù)不是一個(gè)定值,而是需要根據(jù)業(yè)務(wù)場(chǎng)景的復(fù)雜度決定。使用分層模型中需要注意,一般來(lái)說(shuō)不能跨層、同層或反向調(diào)用,否則會(huì)讓整個(gè)層次模型由單一的樹(shù)形結(jié)構(gòu)變?yōu)榫W(wǎng)狀結(jié)構(gòu),失去了分層的意義。

但隨著程序復(fù)雜度的逐漸提升,要嚴(yán)格的按照分層模型逐級(jí)調(diào)用的話(huà),會(huì)產(chǎn)生很多無(wú)用的空層,它們的作用只是傳遞請(qǐng)求,這也違背了軟件設(shè)計(jì)盡量簡(jiǎn)潔的方向。所以實(shí)際場(chǎng)景中可以對(duì)各個(gè)層次規(guī)定“開(kāi)放”或“關(guān)閉”屬性,對(duì)于“開(kāi)放”的層次,上層可以越過(guò)這層,直接訪問(wèn)下層。

對(duì)層次定義“開(kāi)放”或“關(guān)閉”可以幫助程序員更好的理解各層次之間的交互,這類(lèi)約定需要記錄在文檔中,并且確保團(tuán)隊(duì)中的每個(gè)人都了解這些約定,否則會(huì)得到一個(gè)復(fù)雜的、難以維護(hù)的工程。

事件驅(qū)動(dòng)架構(gòu)

事件驅(qū)動(dòng)架構(gòu)能比較好的解耦請(qǐng)求方和處理方,經(jīng)常被用在寫(xiě)入請(qǐng)求量變化較大,或者是請(qǐng)求方不關(guān)心處理邏輯的場(chǎng)景中,它有兩種主要的實(shí)現(xiàn)方式:

Mediator

在mediator方式中,存在一個(gè)中介者角色,它接收寫(xiě)入方的請(qǐng)求,并把事件分配到對(duì)應(yīng)的處理方(圖中的channel),每個(gè)處理方只需要關(guān)心自己的channel,而不需要與寫(xiě)入方直接通信。

在微博前些年應(yīng)用比較多的應(yīng)用服務(wù)-隊(duì)列-消息處理服務(wù)可以認(rèn)為是屬于這種模式。

Broker

在broker方式中不存在中介者的角色,取而代之的是消息流在輕量的processor中流轉(zhuǎn),形成一個(gè)消息處理的鏈路,如圖:

前一段時(shí)間開(kāi)始推廣的storm流式處理屬于這種模式,對(duì)于較長(zhǎng)的處理流程,用broker方式可以避免實(shí)現(xiàn)Mediator的復(fù)雜性,相對(duì)的,管理整個(gè)流程變得復(fù)雜了。

微內(nèi)核架構(gòu)(Microkernel)

微內(nèi)核架構(gòu)相對(duì)于普通架構(gòu)最主要的區(qū)別是多了“內(nèi)核”的概念,在編寫(xiě)程序時(shí)把基礎(chǔ)功能和擴(kuò)展功能分離:內(nèi)核中不再實(shí)現(xiàn)具體功能,而是定義“擴(kuò)展點(diǎn)”,增加功能時(shí)不再修改主邏輯,而是通過(guò)“擴(kuò)展點(diǎn)”掛接到內(nèi)核中,如圖:

之前介紹的motan RPC框架應(yīng)用了這種設(shè)計(jì),這讓motan可以通過(guò)不同的插件支持更多的功能,比如增加傳輸協(xié)議、定義新的服務(wù)發(fā)現(xiàn)規(guī)則等。

微服務(wù)架構(gòu)

近年來(lái)微服務(wù)架構(gòu)的概念一直比較火,它可以解決服務(wù)逐漸增長(zhǎng)之后造成的難以測(cè)試及部署、資源浪費(fèi)等問(wèn)題,但也帶來(lái)了服務(wù)調(diào)度和服務(wù)發(fā)現(xiàn)層面的復(fù)雜度,如圖:

微博底層實(shí)際包含了很多業(yè)務(wù)邏輯,這些業(yè)務(wù)邏輯被抽象成一個(gè)個(gè)服務(wù)和模塊,不同模塊之間通過(guò)motan rpc、grpc或http rest api進(jìn)行通信,通過(guò)docker和之上的調(diào)度服務(wù)進(jìn)行調(diào)度和部署,最終成為一個(gè)完整的系統(tǒng)。

微服務(wù)隔離了各服務(wù)之間的耦合,能夠有效提升開(kāi)發(fā)效率;除此之外,當(dāng)微博面對(duì)巨大的流量峰值時(shí),可以進(jìn)行更精細(xì)的資源調(diào)配和更有效率的部署。

單元化架構(gòu)

傳統(tǒng)的分層架構(gòu)往往會(huì)存在一些中心節(jié)點(diǎn),如數(shù)據(jù)庫(kù)、緩存等,這些節(jié)點(diǎn)往往容易成為性能瓶頸,并且存在擴(kuò)容比較復(fù)雜的問(wèn)題。

在面臨對(duì)擴(kuò)展性和性能有極端要求的場(chǎng)景時(shí),可以考慮使用單元化架構(gòu):對(duì)數(shù)據(jù)進(jìn)行切分,并將每一部分?jǐn)?shù)據(jù)及相關(guān)的邏輯部署在同一個(gè)節(jié)點(diǎn)中,如圖:

在單元化架構(gòu)中,每個(gè)“單元”都可以獨(dú)立部署,單元中包括獨(dú)立的計(jì)算和存儲(chǔ)模塊;計(jì)算模塊只與單元內(nèi)的存儲(chǔ)模塊交互,不再需要分庫(kù)分表等邏輯;而數(shù)據(jù)與存儲(chǔ)更近也降低了網(wǎng)絡(luò)消耗,進(jìn)而提高了效率。

微博平臺(tái)通過(guò)對(duì)群發(fā)服務(wù)的單元化改造,實(shí)現(xiàn)了百萬(wàn)級(jí)每秒的私信推送服務(wù)。

如何處理遺留代碼

對(duì)于一個(gè)不斷發(fā)展的系統(tǒng),必然有一些遺留下來(lái)的歷史問(wèn)題。當(dāng)遇到了遺留下來(lái)的爛代碼時(shí),除了理解和修改代碼,更重要的是如何讓代碼朝著好的方向發(fā)展,而不是放任不管。

重構(gòu)是處理遺留代碼的比較重要的手段,這一節(jié)主要討論一下重構(gòu)的相關(guān)話(huà)題。

何時(shí)重構(gòu)

新同學(xué)往往對(duì)重構(gòu)抱有恐懼感,認(rèn)為重構(gòu)應(yīng)該找一個(gè)比較長(zhǎng)的時(shí)間專(zhuān)門(mén)去做。這種愿望很好,但是現(xiàn)實(shí)中一般很難找出一段相對(duì)穩(wěn)定的時(shí)間。

另一方面,重構(gòu)是比較考驗(yàn)編程水平的一項(xiàng)工作。代碼寫(xiě)的不好的人,即使做了重構(gòu)也只是把不好的代碼變了個(gè)形式。要達(dá)到比較高的水平往往需要不斷的練習(xí),幾個(gè)月做一次重構(gòu)很難得到鍛煉,重構(gòu)效果也會(huì)打折。

所以,重構(gòu)最好是能夠作為一項(xiàng)日常工作,在開(kāi)發(fā)時(shí)對(duì)剛寫(xiě)完的代碼做重構(gòu)往往單位時(shí)間的收益是最大的。

如何重構(gòu)

一般來(lái)說(shuō),重構(gòu)可以抽象成四個(gè)方面:

理解現(xiàn)狀

如果對(duì)當(dāng)前程序的理解是錯(cuò)的,那么重構(gòu)之后的正確性也就無(wú)從談起。所以在重構(gòu)之前需要理解待重構(gòu)的代碼做了什么,這個(gè)過(guò)程中可以伴隨一些小的、基本無(wú)風(fēng)險(xiǎn)的重構(gòu),例如重命名變量、提取內(nèi)部方法等,以幫助我們理解程序。

理解目標(biāo)

在理解了程序做了什么事情之后,第二個(gè)需要做的事情就是需要提前想好重構(gòu)之后的代碼是什么樣的。

改變代碼結(jié)構(gòu)比較復(fù)雜,并且往往伴隨著風(fēng)險(xiǎn)和不可控的問(wèn)題。所以在實(shí)際動(dòng)手之前,需要從更高的層次考慮重構(gòu)之后的模塊如何劃分,交互是如何控制等等,在這個(gè)過(guò)程中實(shí)際與寫(xiě)代碼要做的事情是一致的。

劃分范圍

爛代碼往往模塊的劃分有一些問(wèn)題,在重構(gòu)時(shí)牽一發(fā)而動(dòng)全身,改的越多問(wèn)題越多,導(dǎo)致重構(gòu)過(guò)程不可控。所以在動(dòng)手重構(gòu)前需要想辦法減少重構(gòu)改動(dòng)的范圍,一般來(lái)說(shuō)可以只改動(dòng)相鄰層次的幾個(gè)類(lèi),并且只改動(dòng)一個(gè)功能相關(guān)的代碼,不求一次性全部改完。

為了能夠劃分范圍,在重構(gòu)時(shí)需要采用一些方法解除掉依賴(lài)鏈。比如增加適配器等等,這些類(lèi)可能只是臨時(shí)的,在完整的重構(gòu)完成之后就可以刪除掉,看起來(lái)是增加了工作量,但是換來(lái)的是更可控的影響范圍。

確保正確

為了能保證重構(gòu)的正確性,需要一些測(cè)試來(lái)驗(yàn)證重構(gòu)是否安全。最有效的是單元測(cè)試,它能提供比集成測(cè)試更高的覆蓋率,也能驗(yàn)證重構(gòu)之后的代碼設(shè)計(jì)是否是合理的。

在做一次重構(gòu)之前需要整理模塊的單元測(cè)試。遺留代碼有可能測(cè)試不全,并且難以編寫(xiě)單元測(cè)試,此時(shí)可以適當(dāng)?shù)臓奚貥?gòu)代碼的優(yōu)雅性,比如提高私有方法的可見(jiàn)性,滿(mǎn)足測(cè)試的需求。在重構(gòu)的過(guò)程中,這部分代碼會(huì)被逐漸替換掉。

總結(jié)

今天跟大家討論了一下關(guān)于編程的各個(gè)方面,關(guān)于編程的話(huà)題看似很基礎(chǔ),想要做好卻并不容易。新同學(xué)比較容易急于求成,往往過(guò)多的關(guān)注架構(gòu)或者某些新技術(shù),卻忽視了基本功的修煉,而在后續(xù)的工作過(guò)程中,基本功不扎實(shí)的人做事往往會(huì)事倍功半,難以有更一步的發(fā)展。

勿在浮沙筑高臺(tái),與各位共勉。

新兵訓(xùn)練營(yíng)簡(jiǎn)介

微博平臺(tái)新兵訓(xùn)練營(yíng)活動(dòng)是微博平臺(tái)內(nèi)部組織的針對(duì)新入職同學(xué)的團(tuán)隊(duì)融入培訓(xùn)課程,目標(biāo)是團(tuán)隊(duì)融入,包括人的融入,氛圍融入,技術(shù)融入。當(dāng)前已經(jīng)進(jìn)行4期活動(dòng),很多學(xué)員迅速成長(zhǎng)為平臺(tái)技術(shù)骨干。

具體課程包括《環(huán)境與工具》《分布式緩存介紹》《海量數(shù)據(jù)存儲(chǔ)基礎(chǔ)》《平臺(tái)RPC框架介紹》《平臺(tái)Web框架》《編寫(xiě)優(yōu)雅代碼》《一次服務(wù)上線》《Feed架構(gòu)介紹》《unread架構(gòu)介紹》

微博平臺(tái)是非常注重團(tuán)隊(duì)成員融入與成長(zhǎng)的團(tuán)隊(duì),在這里有人幫你融入,有人和你一起成長(zhǎng),也歡迎小伙伴們加入微博平臺(tái),歡迎私信咨詢(xún)。

講師簡(jiǎn)介

秦迪,@蛋疼的AXB?微博平臺(tái)及大數(shù)據(jù)部技術(shù)專(zhuān)家

個(gè)人介紹:http://blog.2baxb.me/about-me

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)