代碼的壞味道

并不能給一個何時必須重構(gòu)的精確衡量標(biāo)準(zhǔn)骑素,只能給出一些跡象,它會指出“這里有一個可以用重構(gòu)解決的問題”莽红。比如一個類有多少變量算是太大醉蚁,一個函數(shù)有多少代碼才算太長黔龟。本文給出一些代碼得壞味道观谦,可以作為重構(gòu)的切入點

1 神秘命名(Mysterious Name)

整潔代碼最重要的一環(huán)就是好的明字倒得,然而命名是編程中最難的兩件事之一菩彬。正因為如此,改名可能是最常用的重構(gòu)手法,包括改變方法名帆竹、變量名、字段名等态罪。
如果我想不出一個好明字耗啦,說明背后很可能潛藏著更深的設(shè)計問題。個人理解好的命名應(yīng)該是能清晰的表明自己的功能和用法。

2 重復(fù)代碼(Duplicated Code)

如果在多個地方看到相同的代碼結(jié)構(gòu)堵未,那么可以肯定,設(shè)法將她們合而為一,程序會變得更好岛心。

  • 最簡單得重復(fù)代碼就是,兩個函數(shù)有相同的代碼塊送朱,這個時候只需要把相同的代碼塊提出來就可以了回怜。
  • 如果重復(fù)代碼只是相似,而不是完全不同,可以首先重新組織代碼順序,把相似的部分放在一起咳焚,以便提煉。
  • 如果重復(fù)的代碼位于同一個父類的多個子類中,可以把代碼移動到父類中。

3 過長函數(shù)(Long Function)

根據(jù)經(jīng)驗审编,活得最長件炉、最好的程序宫静,其中的函數(shù)都比較短橘洞。初次接觸到這種代碼庫的時候,往往會覺得程序里滿是無窮無盡的委托調(diào)用,但是間接性帶來的好處就是更好的闡釋力、更易于分享辩稽、更多的選擇。
小函數(shù)會給代碼的閱讀者帶來一些負(fù)擔(dān)侮腹,因為必須經(jīng)常切換上下文望抽,才能明白函數(shù)在做什么毁腿,但現(xiàn)代的開發(fā)環(huán)境胯究,可以讓你快速的跳轉(zhuǎn)。小函數(shù)易于理解的關(guān)鍵還是在于良好的命名株婴,如果你能給函數(shù)起個好明字,閱讀代碼的人就可以通過明字了解函數(shù)的作用八回,根本不必去看其中寫了些什么乍迄。
我們遵循這樣一條原則:每當(dāng)感覺需要以注釋來說明點什么的時候谅将,就需要把說明的代碼塊隅熙,寫進(jìn)一個獨立函數(shù)中弯淘,并以其用途命名态鳖。
如果遺留代碼前方惨寿,有一行注釋,就是在提醒你:可以將這段代碼替換成一個函數(shù),而且可以在注釋的基礎(chǔ)上給這個函數(shù)命名闸准。
條件表達(dá)式和循環(huán)通常也是提煉的信號敏释,對于龐大的switch語句耳鸯,其中的每個分支都應(yīng)該提煉成函數(shù)财喳。如果你發(fā)現(xiàn)提煉出的循環(huán)很難命名,可能是因為其中做了幾件不同的事泌枪,這種情況請勇敢的拆分循環(huán)碌燕,將其拆分成各自獨立的任務(wù)修壕。

4 過長參數(shù)列表(Long Parameter List)

如果可以向某個參數(shù)發(fā)起查詢而獲得另一個參數(shù)的值遏考,那么就可以發(fā)起查詢?nèi)サ暨@個參數(shù)傳遞诈皿。
如果有幾項參數(shù)總是同時出現(xiàn),可以用引入?yún)?shù)對象將其合并為一個對象截歉。
如果多個函數(shù)有同樣的幾個參數(shù)瘪松,引入一個類就尤為有意義记罚,可以將函數(shù)組合成類。

5 全局?jǐn)?shù)據(jù)(Global Data)

如果數(shù)據(jù)在程序啟動之后不再修改,這樣的全局?jǐn)?shù)據(jù)就算相對安全。
全局?jǐn)?shù)據(jù)的問題在于梭纹,從代碼庫的任何一個角落都可以修改它,一次又一次,全局?jǐn)?shù)據(jù)造成的那些詭異bug,問題的根源卻在遙遠(yuǎn)的別處芳悲,想要找出錯誤的代碼難于登天。全局?jǐn)?shù)據(jù)最顯而易見的形式就是全局變量肮韧,但變量和單例也有這樣的問題弄企。
首要的防御手段是封裝變量拘领,每當(dāng)看到可能被各處代碼污染的數(shù)據(jù)约素,就應(yīng)該把變量封裝到一個類或模塊中业汰,只允許模塊內(nèi)的代碼使用它为障,其他地方只能通過封裝的函數(shù)來訪問這個全局變量,從而盡量控制其作用域。
良藥與毒藥的區(qū)別在于劑量侦香,有少量的全局?jǐn)?shù)據(jù)也無妨,但數(shù)量越多散吵,處理的難度就會指數(shù)上升矾睦。

6 可變數(shù)據(jù)(Mutable Data)

我在一處更新數(shù)據(jù)枚冗,卻沒有意識到軟件的另一處期望著完全不同的數(shù)據(jù)官紫,于是一個功能失效了州藕∑堆撸可以用封裝變量來確保所有數(shù)據(jù)更新操作,都通過有限的幾個函數(shù)來進(jìn)行珍德。將查詢函數(shù)和修改函數(shù)分離虑稼,確保調(diào)用者不會調(diào)到有副作用的代碼验烧。
如果可變數(shù)據(jù)的值能在其他地方計算出來弧满,這就是一個特別刺鼻的壞味道摹迷。如果變量作用域只有幾行代碼近哟,即使其中的數(shù)據(jù)可變,也不是什么大問題蜡秽;但隨著變量作用域的擴(kuò)展,風(fēng)險也隨之增大。

7 發(fā)散式變化(Divergent Change)

TODO:區(qū)分霰彈式修改
我們希望軟件能夠容易被修改斗躏。一旦需要修改啄糙,我們希望只改動系統(tǒng)的某一點静陈,只在該處做修改鲸拥。如果不能做到這一點刑赶,這就是一個刺鼻的壞味道了撞叨。
如果新出現(xiàn)一種工具牵敷,我必須修改4個函數(shù)枷餐,這就是發(fā)散式變化的征兆毛肋〈迳可以通過搬移函數(shù)趁桃、提煉函數(shù)、提煉類等方法典徘,消除發(fā)散式變化益咬。

8 霰彈式修改(Shortgun Surgery)

如果需要修改的代碼散步四處逮诲,你不但很難找到他們,也很容易錯過某個重要的修改幽告。這種情況下梅鹦,應(yīng)該使用搬移函數(shù)和搬移字段把所有需要修改的代碼放進(jìn)同一個模塊里。
面對霰彈式修改齐唆,一個常用的策略就是使用與內(nèi)聯(lián)相關(guān)的重構(gòu),把不應(yīng)該分散的邏輯拽回一處冻河。

9 依戀情結(jié)(Feature Envy)

所謂模塊化箍邮,就是力求將代碼分出區(qū)域口芍,最大化區(qū)域內(nèi)部的交互雹仿、最小化跨區(qū)域的交互。但有時你會發(fā)現(xiàn)柜裸,一個函數(shù)跟另一個模塊中的函數(shù)或者數(shù)據(jù)交流格外頻繁擂错,遠(yuǎn)勝于在自己所處模塊內(nèi)部的交流味滞,這就是依戀情結(jié)的典型情況。
療法顯而易見:這個函數(shù)想跟這些數(shù)據(jù)待在一起攒暇,就使用搬移函數(shù)把它移過去。當(dāng)然子房,并非所有情況都這么簡單形用,一個函數(shù)往往會用到幾個模塊的功能,那么它究竟該被置于何處呢证杭,我們的原則是:判斷哪個模塊擁有的此函數(shù)使用的數(shù)據(jù)最多田度,然后就把這個函數(shù)和那些數(shù)據(jù)擺在一起。如果先以提煉函數(shù)將這個函數(shù)分解為數(shù)個較小的函數(shù)解愤,并分別置于不同地點镇饺,上述步驟也就比較容易完成。

10 數(shù)據(jù)泥團(tuán)(Data Clump)

數(shù)據(jù)項就像小孩子送讲,喜歡成群結(jié)隊地待在一塊奸笤。你常惩锟校可以在很多地方看到相同的三四項數(shù)據(jù):兩個類中相同的字段、許多函數(shù)簽名中相同的參數(shù)监右。這些總是綁在一起出現(xiàn)地數(shù)據(jù)边灭,應(yīng)該擁有他們自己地對象。

11 基本類型偏執(zhí)(Primitive Obsession)

大多數(shù)編程環(huán)境都大量使用基本類型健盒,即整數(shù)绒瘦、浮點數(shù)和字符串等。我們經(jīng)晨垩ⅲ看到把錢當(dāng)作普通數(shù)字來計算地情況惰帽、計算物理量時無視單位地情況以及大量類似

if (a < upper ** a > lower)

這樣地代碼〔可以用對象取待基本類型善茎,將原本單獨存在地數(shù)據(jù)值替換為對象,從而走出傳統(tǒng)地洞窟频轿,進(jìn)入炙手可熱地對象世界。

12 重復(fù)地switch(Repeated Switches)

在不同地地方反復(fù)使用同樣地switch邏輯(可能是switch-case語句烁焙,也可能是連續(xù)地if-else語句)航邢。重復(fù)的switch問題在于:每當(dāng)你想增加一個選擇分支時,必須找到所有地switch骄蝇,并逐一更新膳殷。多態(tài)給了我們對抗這種黑暗力量地武器,使我們得到更優(yōu)雅地代碼庫九火。

13 循環(huán)語句(Loops)

Java中地stream中各種filter赚窃、map操作,可以幫助我們更快地看清被處理地元素以及處理他們地動作岔激。

14 冗贅地元素(Lazy Element)

可能有這樣一個函數(shù)勒极,她的名字就跟實現(xiàn)代碼看起來一摸一樣,也可能有這樣一個類虑鼎,根本就是一個簡單的函數(shù)辱匿。這可能是因為,起初在編寫這個函數(shù)時炫彩,程序員也許希望它將來有一天會變大匾七、變復(fù)雜,但那一天從未到來江兢;也可能是因為昨忆,這個類原本是有用的,但隨著需求的迭代與重構(gòu)的進(jìn)行越來越小杉允,最后只剩了一個函數(shù)邑贴。無論上述哪一種原因席里,請讓這樣的程序元素莊嚴(yán)赴義吧。

15 夸夸其談通用性(Speculative Generality)

當(dāng)有人說“我想我們總有一天需要做這事”痢缎,并因而企圖以各式各樣的鉤子和特殊情況來處理一些非必要的事情胁勺,這種壞味道就出現(xiàn)了。這么做的結(jié)果往往造成系統(tǒng)更難理解和維護(hù)独旷。

16 臨時字段(Temporary Field)

有時你會看到這樣的類:其內(nèi)部某個字段僅為某種特定情況而設(shè)署穗,這樣的代碼讓人不易理解,因為你通常認(rèn)為對象在所有時候需要它的所有字段嵌洼。在字段未被使用的情況下猜測當(dāng)初設(shè)置它的目的案疲,會讓你發(fā)瘋。
請使用提煉類給這個可憐的孤兒創(chuàng)造一個家麻养,然后用搬移函數(shù)把所有和這些字段相關(guān)的代碼都放進(jìn)這個新家褐啡。

17 過長的消息鏈(Message Chains)

如果你看到用戶向一個對象A請求另一個對象B,然后再向B請求另一個對象C鳖昌,然后再向C請求對象D......這就是消息鏈备畦。在實際代碼中你看到的可能是一長串取值函數(shù)或一長串臨時變量。采用這種方式许昨,意味客戶端代碼將與查找過程中的導(dǎo)航結(jié)構(gòu)緊密耦合懂盐。一旦對象間的關(guān)系發(fā)生任何變化,客戶端就不得不做出相應(yīng)修改糕档。
通常更好的選擇是:先觀察消息鏈最終得到的對象是用來干什么的莉恼,看看能否以提煉函數(shù)把使用該對象的代碼提煉到一個獨立的函數(shù)中,再運用搬移函數(shù)把這個函數(shù)推入消息鏈速那。如果還有許多客戶端代碼需要訪問鏈上的其他對象俐银,同樣添加一個函數(shù)來完成此事。

18 中間人(Middle Man)

也許你會看到某個類的接口有一半的函數(shù)都委托給其他類端仰,這就是過度運用捶惜,這時應(yīng)該使用移除中間人,直接和真正負(fù)責(zé)的對象打交道榆俺。如果這樣“不干實事”的函數(shù)只有少數(shù)幾個售躁,可以運用內(nèi)聯(lián)函數(shù)把他們放進(jìn)調(diào)用段。

19 內(nèi)幕交易(Insider Trading)

兩個模塊之間茴晋,一定的數(shù)據(jù)交換不可避免陪捷,但我們必須盡量減少這種情況,并把這種交換都放到明面上來诺擅。如果兩個模塊總是竊竊私語市袖,就應(yīng)該用搬移函數(shù)和搬移字段,減少她們的私下交流。如果兩個模塊有共同的興趣苍碟,可以嘗試再新建一個模塊酒觅,把這些公用的數(shù)據(jù)放在一個管理良好的地方。

20 過大的類(Large Class)

如果想利用單個類做太多事情微峰,其內(nèi)往往就會出現(xiàn)太多字段舷丹。一旦如此,重復(fù)代碼也就接踵而至了蜓肆。通常颜凯,如果類內(nèi)的數(shù)個變量有著相同的前綴或者后綴,這就意味著有機(jī)會把她們提煉到某個組件內(nèi)仗扬。
觀察一個大類的使用者症概,經(jīng)常能找到如何拆分類的線索,看看使用者是否只用到了這個類所有功能的一個子集早芭,每個這樣的子集都可能拆分成一個獨立的類彼城。

21 異曲同工的類(Alternative Classes with Different Interfaces)

使用類的好處之一就在于可以替換:今天用這個類,未來可以換成用另一個類退个。但只有兩個類的接口一致時募壕,才能做這種替換。

22 純數(shù)據(jù)類(Data Class)

所謂純數(shù)據(jù)類是指语盈,她們擁有一些字段司抱,以及用于訪問這些字段的函數(shù),除此之外一無長物黎烈。這樣的類只是一種不會說話的數(shù)據(jù)容器,她們幾乎一定被其他類過分細(xì)瑣的操控著匀谣。
純數(shù)據(jù)類常常意味著行為被放在了錯誤的地方照棋。也就是說,只要把處理數(shù)據(jù)的行為從客戶端搬移到純數(shù)據(jù)類里來武翎,就能使情況大為改觀烈炭。
但也要有例外情況,純數(shù)據(jù)記錄對象被用作函數(shù)調(diào)用的返回結(jié)果宝恶,這種結(jié)果數(shù)據(jù)對象有一個關(guān)鍵的特征:她是不可修改的符隙,不可修改的字段無需封裝。

23 被拒絕的遺贈(Refused Bequest)

子類應(yīng)該繼承父類的函數(shù)和數(shù)據(jù)垫毙。但如果她們不想或不需要繼承霹疫,就意味著繼承體系設(shè)計錯誤。你需要為這個子類新建一個兄弟類综芥,把不需要的函數(shù)和數(shù)據(jù)丽蝎,推給那個兄弟。這樣一來膀藐,父類就只持有所有子類共享的東西屠阻。

24 注釋(Comments)

別擔(dān)心红省,我們并不是說不該寫注釋。常常會有這樣的情況:你看到一段代碼有著長長的注釋国觉,然后發(fā)現(xiàn)這些注釋之所以存在吧恃,是因為代碼很糟糕。
注釋可以帶我們找到前面提到的壞味道麻诀,然后以各種重構(gòu)手法把壞味道去除痕寓,完成之后我們常常會發(fā)現(xiàn):注釋已經(jīng)變得多余了,因為代碼已經(jīng)清楚得說明了一切针饥。

寫到最后:當(dāng)你感覺需要撰寫注釋時厂抽,請先嘗試重構(gòu),試著讓所有注釋變得多余丁眼。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末筷凤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子苞七,更是在濱河造成了極大的恐慌藐守,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹂风,死亡現(xiàn)場離奇詭異卢厂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)惠啄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進(jìn)店門慎恒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人撵渡,你說我怎么就攤上這事融柬。” “怎么了趋距?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵粒氧,是天一觀的道長。 經(jīng)常有香客問我节腐,道長外盯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任翼雀,我火速辦了婚禮饱苟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锅纺。我一直安慰自己掷空,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著坦弟,像睡著了一般护锤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酿傍,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天烙懦,我揣著相機(jī)與錄音,去河邊找鬼赤炒。 笑死氯析,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的莺褒。 我是一名探鬼主播掩缓,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼遵岩!你這毒婦竟也來了你辣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤尘执,失蹤者是張志新(化名)和其女友劉穎舍哄,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體誊锭,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡表悬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了丧靡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蟆沫。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖温治,靈堂內(nèi)的尸體忽然破棺而出饥追,到底是詐尸還是另有隱情,我是刑警寧澤罐盔,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站救崔,受9級特大地震影響惶看,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜六孵,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一纬黎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧劫窒,春花似錦本今、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挪凑。三九已至,卻和暖如春逛艰,著一層夾襖步出監(jiān)牢的瞬間躏碳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工散怖, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留菇绵,地道東北人。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓镇眷,卻偏偏與公主長得像咬最,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子欠动,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,654評論 2 354

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