<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          常見代碼重構(gòu)技巧(非常實(shí)用)

          共 31536字,需瀏覽 64分鐘

           ·

          2021-06-03 19:32

          不點(diǎn)藍(lán)字,我們哪來故事?

          每天 11 點(diǎn)更新文章,餓了點(diǎn)外賣,點(diǎn)擊 ??《無門檻外賣優(yōu)惠券,每天免費(fèi)領(lǐng)!》

          來源:juejin.cn/post/6954378167947624484

          • 關(guān)于重構(gòu)
            • 為什么要重構(gòu)
            • 什么是重構(gòu)
          • 代碼的壞味道
            • 壞代碼的問題
            • 什么是好代碼
          • 如何重構(gòu)
            • SOLID原則
            • 設(shè)計(jì)模式
            • 代碼分層
            • 命名規(guī)范
            • 重構(gòu)技巧
          • 質(zhì)量如何保證
            • 測試驅(qū)動(dòng)開發(fā)
            • TDD的開發(fā)周期
            • 兩個(gè)基本的原則
            • 分層測試點(diǎn)

          關(guān)于重構(gòu)

          為什么要重構(gòu)

          項(xiàng)目在不斷演進(jìn)過程中,代碼不停地在堆砌。如果沒有人為代碼的質(zhì)量負(fù)責(zé),代碼總是會(huì)往越來越混亂的方向演進(jìn)。當(dāng)混亂到一定程度之后,量變引起質(zhì)變,項(xiàng)目的維護(hù)成本已經(jīng)高過重新開發(fā)一套新代碼的成本,想要再去重構(gòu),已經(jīng)沒有人能做到了。

          造成這樣的原因往往有以下幾點(diǎn):

          1. 編碼之前缺乏有效的設(shè)計(jì)
          2. 成本上的考慮,在原功能堆砌式編程
          3. 缺乏有效代碼質(zhì)量監(jiān)督機(jī)制

          對(duì)于此類問題,業(yè)界已有有很好的解決思路:通過持續(xù)不斷的重構(gòu)將代碼中的“壞味道”清除掉。

          什么是重構(gòu)

          重構(gòu)一書的作者M(jìn)artin Fowler對(duì)重構(gòu)的定義:

          重構(gòu)(名詞):對(duì)軟件內(nèi)部結(jié)構(gòu)的一種調(diào)整,目的是在不改變軟件可觀察行為的前提下,提高其可理解性,降低其修改成本。重構(gòu)(動(dòng)詞):使用一系列重構(gòu)手法,在不改變軟件可觀察行為的前提下,調(diào)整其結(jié)構(gòu)。

          根據(jù)重構(gòu)的規(guī)模可以大致分為大型重構(gòu)和小型重構(gòu):

          大型重構(gòu) :對(duì)頂層代碼設(shè)計(jì)的重構(gòu),包括:系統(tǒng)、模塊、代碼結(jié)構(gòu)、類與類之間的關(guān)系等的重構(gòu),重構(gòu)的手段有:分層、模塊化、解耦、抽象可復(fù)用組件等等。這類重構(gòu)的工具就是我們學(xué)習(xí)過的那些設(shè)計(jì)思想、原則和模式。這類重構(gòu)涉及的代碼改動(dòng)會(huì)比較多,影響面會(huì)比較大,所以難度也較大,耗時(shí)會(huì)比較長,引入bug的風(fēng)險(xiǎn)也會(huì)相對(duì)比較大。

          小型重構(gòu) :對(duì)代碼細(xì)節(jié)的重構(gòu),主要是針對(duì)類、函數(shù)、變量等代碼級(jí)別的重構(gòu),比如規(guī)范命名和注釋、消除超大類或函數(shù)、提取重復(fù)代碼等等。小型重構(gòu)更多的是使用統(tǒng)一的編碼規(guī)范。這類重構(gòu)要修改的地方比較集中,比較簡單,可操作性較強(qiáng),耗時(shí)會(huì)比較短,引入bug的風(fēng)險(xiǎn)相對(duì)來說也會(huì)比較小。什么時(shí)候重構(gòu) 新功能開發(fā)、修bug或者代碼review中出現(xiàn)“代碼壞味道”,我們就應(yīng)該及時(shí)進(jìn)行重構(gòu)。持續(xù)在日常開發(fā)中進(jìn)行小重構(gòu),能夠降低重構(gòu)和測試的成本。

          代碼的壞味道

          代碼重復(fù)

          • 實(shí)現(xiàn)邏輯相同、執(zhí)行流程相同

          方法過長

          • 方法中的語句不在同一個(gè)抽象層級(jí)
          • 邏輯難以理解,需要大量的注釋
          • 面向過程編程而非面向?qū)ο?/section>

          過大的類

          • 類做了太多的事情
          • 包含過多的實(shí)例變量和方法
          • 類的命名不足以描述所做的事情

          邏輯分散

          • 發(fā)散式變化:某個(gè)類經(jīng)常因?yàn)椴煌脑蛟诓煌姆较蛏习l(fā)生變化
          • 散彈式修改:發(fā)生某種變化時(shí),需要在多個(gè)類中做修改

          嚴(yán)重的情結(jié)依戀

          • 某個(gè)類的方法過多的使用其他類的成員

          數(shù)據(jù)泥團(tuán)/基本類型偏執(zhí)

          • 兩個(gè)類、方法簽名中包含相同的字段或參數(shù)
          • 應(yīng)該使用類但使用基本類型,比如表示數(shù)值與幣種的Money類、起始值與結(jié)束值的Range類

          不合理的繼承體系

          • 繼承打破了封裝性,子類依賴其父類中特定功能的實(shí)現(xiàn)細(xì)節(jié)
          • 子類必須跟著其父類的更新而演變,除非父類是專門為了擴(kuò)展而設(shè)計(jì),并且有很好的文檔說明

          過多的條件判斷

          過長的參數(shù)列

          臨時(shí)變量過多

          令人迷惑的暫時(shí)字段

          • 某個(gè)實(shí)例變量僅為某種特定情況而設(shè)置
          • 將實(shí)例變量與相應(yīng)的方法提取到新的類中

          純數(shù)據(jù)類

          • 僅包含字段和訪問(讀寫)這些字段的方法
          • 此類被稱為數(shù)據(jù)容器,應(yīng)保持最小可變性

          不恰當(dāng)?shù)拿?/span>

          • 命名無法準(zhǔn)確描述做的事情
          • 命名不符合約定俗稱的慣例

          過多的注釋

          壞代碼的問題

          • 難以復(fù)用
          • 系統(tǒng)關(guān)聯(lián)性過多,導(dǎo)致很難分離可重用部分
          • 難于變化
          • 一處變化導(dǎo)致其他很多部分的修改,不利于系統(tǒng)穩(wěn)定
          • 難于理解
          • 命名雜亂,結(jié)構(gòu)混亂,難于閱讀和理解
          • 難以測試
          • 分支、依賴較多,難以覆蓋全面

          什么是好代碼

          代碼質(zhì)量的評(píng)價(jià)有很強(qiáng)的主觀性,描述代碼質(zhì)量的詞匯也有很多,比如可讀性、可維護(hù)性、靈活、優(yōu)雅、簡潔。這些詞匯是從不同的維度去評(píng)價(jià)代碼質(zhì)量的。其中,可維護(hù)性、可讀性、可擴(kuò)展性又是提到最多的、最重要的三個(gè)評(píng)價(jià)標(biāo)準(zhǔn)。

          要寫出高質(zhì)量代碼,我們就需要掌握一些更加細(xì)化、更加能落地的編程方法論,這就包含面向?qū)ο笤O(shè)計(jì)思想、設(shè)計(jì)原則、設(shè)計(jì)模式、編碼規(guī)范、重構(gòu)技巧等。

          如何重構(gòu)

          SOLID原則

          4_SOLID原則.png

          單一職責(zé)原則

          一個(gè)類只負(fù)責(zé)完成一個(gè)職責(zé)或者功能,不要存在多于一種導(dǎo)致類變更的原因。

          單一職責(zé)原則通過避免設(shè)計(jì)大而全的類,避免將不相關(guān)的功能耦合在一起,來提高類的內(nèi)聚性。同時(shí),類職責(zé)單一,類依賴的和被依賴的其他類也會(huì)變少,減少了代碼的耦合性,以此來實(shí)現(xiàn)代碼的高內(nèi)聚、松耦合。但是,如果拆分得過細(xì),實(shí)際上會(huì)適得其反,反倒會(huì)降低內(nèi)聚性,也會(huì)影響代碼的可維護(hù)性。

          開放-關(guān)閉原則

          添加一個(gè)新的功能,應(yīng)該是通過在已有代碼基礎(chǔ)上擴(kuò)展代碼(新增模塊、類、方法、屬性等),而非修改已有代碼(修改模塊、類、方法、屬性等)的方式來完成。

          開閉原則并不是說完全杜絕修改,而是以最小的修改代碼的代價(jià)來完成新功能的開發(fā)。

          很多設(shè)計(jì)原則、設(shè)計(jì)思想、設(shè)計(jì)模式,都是以提高代碼的擴(kuò)展性為最終目的的。特別是 23 種經(jīng)典設(shè)計(jì)模式,大部分都是為了解決代碼的擴(kuò)展性問題而總結(jié)出來的,都是以開閉原則為指導(dǎo)原則的。最常用來提高代碼擴(kuò)展性的方法有:多態(tài)、依賴注入、基于接口而非實(shí)現(xiàn)編程,以及大部分的設(shè)計(jì)模式(比如,裝飾、策略、模板、職責(zé)鏈、狀態(tài))。

          里氏替換原則

          子類對(duì)象(object of subtype/derived class)能夠替換程序(program)中父類對(duì)象(object of base/parent class)出現(xiàn)的任何地方,并且保證原來程序的邏輯行為(behavior)不變及正確性不被破壞。

          子類可以擴(kuò)展父類的功能,但不能改變父類原有的功能

          父類中凡是已經(jīng)實(shí)現(xiàn)好的方法(相對(duì)于抽象方法而言),實(shí)際上是在設(shè)定一系列的規(guī)范和契約,雖然它不強(qiáng)制要求所有的子類必須遵從這些契約,但是如果子類對(duì)這些非抽象方法任意修改,就會(huì)對(duì)整個(gè)繼承體系造成破壞。

          接口隔離原則

          調(diào)用方不應(yīng)該依賴它不需要的接口;一個(gè)類對(duì)另一個(gè)類的依賴應(yīng)該建立在最小的接口上。接口隔離原則提供了一種判斷接口的職責(zé)是否單一的標(biāo)準(zhǔn):通過調(diào)用者如何使用接口來間接地判定。如果調(diào)用者只使用部分接口或接口的部分功能,那接口的設(shè)計(jì)就不夠職責(zé)單一。

          依賴反轉(zhuǎn)原則

          高層模塊不應(yīng)該依賴低層模塊,二者都應(yīng)該依賴其抽象;抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象。

          迪米特法則

          一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象保持最少的了解

          合成復(fù)用原則

          盡量使用合成/聚合的方式,而不是使用繼承。

          單一職責(zé)原則告訴我們實(shí)現(xiàn)類要職責(zé)單一;里氏替換原則告訴我們不要破壞繼承體系;依賴倒置原則告訴我們要面向接口編程;接口隔離原則告訴我們?cè)谠O(shè)計(jì)接口的時(shí)候要精簡單一;迪米特法則告訴我們要降低耦合。而開閉原則是總綱,告訴我們要對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉。

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

          設(shè)計(jì)模式:軟件開發(fā)人員在軟件開發(fā)過程中面臨的一般問題的解決方案。這些解決方案是眾多軟件開發(fā)人員經(jīng)過相當(dāng)長的一段時(shí)間的試驗(yàn)和錯(cuò)誤總結(jié)出來的。每種模式都描述了一個(gè)在我們周圍不斷重復(fù)發(fā)生的問題,以及該問題的核心解決方案。

          • 創(chuàng)建型 :主要解決對(duì)象的創(chuàng)建問題,封裝復(fù)雜的創(chuàng)建過程,解耦對(duì)象的創(chuàng)建代碼和使用代碼
          • 結(jié)構(gòu)型 :主要通過類或?qū)ο蟮牟煌M合,解耦不同功能的耦合
          • 行為型 :主要解決的是類或?qū)ο笾g的交互行為的耦合

          代碼分層

          模塊結(jié)構(gòu)說明

          • server_main:配置層,負(fù)責(zé)整個(gè)項(xiàng)目的module管理,maven配置管理、資源管理等;
          • server_application:應(yīng)用接入層,承接外部流量入口,例如:RPC接口實(shí)現(xiàn)、消息處理、定時(shí)任務(wù)等;不要在此包含業(yè)務(wù)邏輯;
          • server_biz:核心業(yè)務(wù)層,用例服務(wù)、領(lǐng)域?qū)嶓w、領(lǐng)域事件等
          • server_irepository:資源接口層,負(fù)責(zé)資源接口的暴露
          • server_repository:資源層,負(fù)責(zé)資源的proxy訪問,統(tǒng)一外部資源訪問,隔離變化。注意:這里強(qiáng)調(diào)的是弱業(yè)務(wù)性,強(qiáng)數(shù)據(jù)性;
          • server_common:公共層,vo、工具等

          代碼開發(fā)要遵守各層的規(guī)范,并注意層級(jí)之間的依賴關(guān)系。

          命名規(guī)范

          一個(gè)好的命名應(yīng)該要滿足以下兩個(gè)約束:

          • 準(zhǔn)確描述所做得事情
          • 格式符合通用的慣例

          如果你覺得一個(gè)類或方法難以命名的時(shí)候,可能是其承載的功能太多了,需要進(jìn)一步拆分。

          約定俗稱的慣例

          類命名

          類名使用大駝峰命名形式,類命通常使用名詞或名詞短語。接口名除了用名詞和名詞短語以外,還可以使用形容詞或形容詞短語,如 Cloneable,Callable 等,表示實(shí)現(xiàn)該接口的類有某種功能或能力。

          方法命名

          方法命名采用小駝峰的形式,首字小寫,往后的每個(gè)單詞首字母都要大寫。和類名不同的是,方法命名一般為動(dòng)詞或動(dòng)詞短語,與參數(shù)或參數(shù)名共同組成動(dòng)賓短語,即動(dòng)詞 + 名詞。一個(gè)好的函數(shù)名一般能通過名字直接獲知該函數(shù)實(shí)現(xiàn)什么樣的功能。

          重構(gòu)技巧

          提煉方法

          多個(gè)方法代碼重復(fù)、方法中代碼過長或者方法中的語句不在一個(gè)抽象層級(jí)。方法是代碼復(fù)用的最小粒度,方法過長不利于復(fù)用,可讀性低,提煉方法往往是重構(gòu)工作的第一步。

          意圖導(dǎo)向編程 :把處理某件事的流程和具體做事的實(shí)現(xiàn)方式分開。

          • 把一個(gè)問題分解為一系列功能性步驟,并假定這些功能步驟已經(jīng)實(shí)現(xiàn)
          • 我們只需把把各個(gè)函數(shù)組織在一起即可解決這一問題
          • 在組織好整個(gè)功能后,我們?cè)诜謩e實(shí)現(xiàn)各個(gè)方法函數(shù)
          /**
            * 1、交易信息開始于一串標(biāo)準(zhǔn)ASCII字符串。
            * 2、這個(gè)信息字符串必須轉(zhuǎn)換成一個(gè)字符串的數(shù)組,數(shù)組存放的此次交易的領(lǐng)域語言中所包含的詞匯元素(token)。
            * 3、每一個(gè)詞匯必須標(biāo)準(zhǔn)化。
            * 4、包含超過150個(gè)詞匯元素的交易,應(yīng)該采用不同于小型交易的方式(不同的算法)來提交,以提高效率。
            * 5、如果提交成功,API返回”true”;失敗,則返回”false”。
            */

          public class Transaction {
            public Boolean commit(String command) {
              Boolean result = true;
              String[] tokens = tokenize(command);
              normalizeTokens(tokens);
              if (isALargeTransaction(tokens)) {
                result = processLargeTransaction(tokens);
              } else {
                result = processSmallTransaction(tokens);
              }
              return result;
            }
          }

          以函數(shù)對(duì)象取代函數(shù)

          將函數(shù)放進(jìn)一個(gè)單獨(dú)對(duì)象中,如此一來局部變量就變成了對(duì)象內(nèi)的字段。然后你可以在同一個(gè)對(duì)象中將這個(gè)大型函數(shù)分解為多個(gè)小型函數(shù)。

          引入?yún)?shù)對(duì)象

          方法參數(shù)比較多時(shí),將參數(shù)封裝為參數(shù)對(duì)象

          移除對(duì)參數(shù)的賦值

          public int discount(int inputVal, int quantity, int yearToDate) {
            if (inputVal > 50) inputVal -= 2;
            if (quantity > 100) inputVal -= 1;
            if (yearToDate > 10000) inputVal -= 4;
            return inputVal;
          }

          public int discount(int inputVal, int quantity, int yearToDate) {
            int result = inputVal;
            if (inputVal > 50) result -= 2;
            if (quantity > 100) result -= 1;
            if (yearToDate > 10000) result -= 4;
            return result;
          }

          將查詢與修改分離

          任何有返回值的方法,都不應(yīng)該有副作用

          • 不要在convert中調(diào)用寫操作,避免副作用
          • 常見的例外:將查詢結(jié)果緩存到本地

          移除不必要臨時(shí)變量

          臨時(shí)變量僅使用一次或者取值邏輯成本很低的情況下

          引入解釋性變量

          將復(fù)雜表達(dá)式(或其中一部分)的結(jié)果放進(jìn)一個(gè)臨時(shí)變量,以此變量名稱來解釋表達(dá)式用途

          if ((platform.toUpperCase().indexOf("MAC") > -1)
              && (browser.toUpperCase().indexOf("IE") > -1) && wasInitialized() && resize > 0) {
            // do something
          }

          final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
          final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
          final boolean wasResized = resize > 0;
          if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {
            // do something
          }

          使用衛(wèi)語句替代嵌套條件判斷

          把復(fù)雜的條件表達(dá)式拆分成多個(gè)條件表達(dá)式,減少嵌套。嵌套了好幾層的if - then-else語句,轉(zhuǎn)換為多個(gè)if語句

          //未使用衛(wèi)語句
          public void getHello(int type) {
              if (type == 1) {
                  return;
              } else {
                  if (type == 2) {
                      return;
                  } else {
                      if (type == 3) {
                          return;
                      } else {
                          setHello();
                      }
                  }
              }
          }

          //使用衛(wèi)語句
          public void getHello(int type) {
              if (type == 1) {
                  return;
              }
              if (type == 2) {
                  return;
              }
              if (type == 3) {
                  return;
              }
              setHello();
          }

          使用多態(tài)替代條件判斷斷

          當(dāng)存在這樣一類條件表達(dá)式,它根據(jù)對(duì)象類型的不同選擇不同的行為。可以將這種表達(dá)式的每個(gè)分支放進(jìn)一個(gè)子類內(nèi)的復(fù)寫函數(shù)中,然后將原始函數(shù)聲明為抽象函數(shù)。

          public int calculate(int a, int b, String operator) {
              int result = Integer.MIN_VALUE;

              if ("add".equals(operator)) {
                  result = a + b;
              } else if ("multiply".equals(operator)) {
                  result = a * b;
              } else if ("divide".equals(operator)) {
                  result = a / b;
              } else if ("subtract".equals(operator)) {
                  result = a - b;
              }
              return result;
          }

          當(dāng)出現(xiàn)大量類型檢查和判斷時(shí),if else(或switch)語句的體積會(huì)比較臃腫,這無疑降低了代碼的可讀性。另外,if else(或switch)本身就是一個(gè)“變化點(diǎn)”,當(dāng)需要擴(kuò)展新的類型時(shí),我們不得不追加if else(或switch)語句塊,以及相應(yīng)的邏輯,這無疑降低了程序的可擴(kuò)展性,也違反了面向?qū)ο蟮拈_閉原則。

          基于這種場景,我們可以考慮使用“多態(tài)”來代替冗長的條件判斷,將if else(或switch)中的“變化點(diǎn)”封裝到子類中。這樣,就不需要使用if else(或switch)語句了,取而代之的是子類多態(tài)的實(shí)例,從而使得提高代碼的可讀性和可擴(kuò)展性。很多設(shè)計(jì)模式使用都是這種套路,比如策略模式、狀態(tài)模式。

          public interface Operation {
            int apply(int a, int b);
          }

          public class Addition implements Operation {
            @Override
            public int apply(int a, int b) {
              return a + b;
            }
          }

          public class OperatorFactory {
              private final static Map<String, Operation> operationMap = new HashMap<>();
              static {
                  operationMap.put("add"new Addition());
                  operationMap.put("divide"new Division());
                  // more operators
              }

              public static Operation getOperation(String operator) {
                  return operationMap.get(operator);
              }
          }

          public int calculate(int a, int b, String operator) {
              if (OperatorFactory .getOperation == null) {
                 throw new IllegalArgumentException("Invalid Operator");
              }
              return OperatorFactory .getOperation(operator).apply(a, b);
          }

          使用異常替代返回錯(cuò)誤碼

          非正常業(yè)務(wù)狀態(tài)的處理,使用拋出異常的方式代替返回錯(cuò)誤碼

          • 不要使用異常處理用于正常的業(yè)務(wù)流程控制


            • 異常處理的性能成本非常高
          • 盡量使用標(biāo)準(zhǔn)異常

          • 避免在finally語句塊中拋出異常


            • 如果同時(shí)拋出兩個(gè)異常,則第一個(gè)異常的調(diào)用棧會(huì)丟失
          • finally塊中應(yīng)只做關(guān)閉資源這類的事情

          //使用錯(cuò)誤碼
          public boolean withdraw(int amount) {
              if (balance < amount) {
                  return false;
              } else {
                  balance -= amount;
                  return true;
              }
          }

          //使用異常
          public void withdraw(int amount) {
              if (amount > balance) {
                  throw new IllegalArgumentException("amount too large");
              }
              balance -= amount;
          }

          引入斷言

          某一段代碼需要對(duì)程序狀態(tài)做出某種假設(shè),以斷言明確表現(xiàn)這種假設(shè)。

          • 不要濫用斷言,不要使用它來檢查“應(yīng)該為真”的條件,只使用它來檢查“一定必須為真”的條件
          • 如果斷言所指示的約束條件不能滿足,代碼是否仍能正常運(yùn)行?如果可以就去掉斷言

          引入Null對(duì)象或特殊對(duì)象

          當(dāng)使用一個(gè)方法返回的對(duì)象時(shí),而這個(gè)對(duì)象可能為空,這個(gè)時(shí)候需要對(duì)這個(gè)對(duì)象進(jìn)行操作前,需要進(jìn)行判空,否則就會(huì)報(bào)空指針。當(dāng)這種判斷頻繁的出現(xiàn)在各處代碼之中,就會(huì)影響代碼的美觀程度和可讀性,甚至增加Bug的幾率。

          空引用的問題在Java中無法避免,但可以通過代碼編程技巧(引入空對(duì)象)來改善這一問題。

          //空對(duì)象的例子
          public class OperatorFactory {
            static Map<String, Operation> operationMap = new HashMap<>();
            static {
              operationMap.put("add"new Addition());
              operationMap.put("divide"new Division());
              // more operators
            }
            public static Optional<Operation> getOperation(String operator) {
              return Optional.ofNullable(operationMap.get(operator));
            }
          }
          public int calculate(int a, int b, String operator) {
            Operation targetOperation = OperatorFactory.getOperation(operator)
               .orElseThrow(() -> new IllegalArgumentException("Invalid Operator"));
            return targetOperation.apply(a, b);
          }

          //特殊對(duì)象的例子
          public class InvalidOp implements Operation {
            @Override
            public int apply(int a, int b)  {
              throw new IllegalArgumentException("Invalid Operator");
            }
          }

          提煉類

          根據(jù)單一職責(zé)原則,一個(gè)類應(yīng)該有明確的責(zé)任邊界。但在實(shí)際工作中,類會(huì)不斷的擴(kuò)展。當(dāng)給某個(gè)類添加一項(xiàng)新責(zé)任時(shí),你會(huì)覺得不值得分離出一個(gè)單獨(dú)的類。于是,隨著責(zé)任不斷增加,這個(gè)類包含了大量的數(shù)據(jù)和函數(shù),邏輯復(fù)雜不易理解。

          此時(shí)你需要考慮將哪些部分分離到一個(gè)單獨(dú)的類中,可以依據(jù)高內(nèi)聚低耦合的原則。如果某些數(shù)據(jù)和方法總是一起出現(xiàn),或者某些數(shù)據(jù)經(jīng)常同時(shí)變化,這就表明它們應(yīng)該放到一個(gè)類中。另一種信號(hào)是類的子類化方式:如果你發(fā)現(xiàn)子類化只影響類的部分特性,或者類的特性需要以不同方式來子類化,這就意味著你需要分解原來的類。

          //原始類
          public class Person {
              private String name;
              private String officeAreaCode;
              private String officeNumber;

              public String getName() {
                  return name;
              }

              public String getTelephoneNumber() {
                  return ("(" + officeAreaCode + ")" + officeNumber);
              }

              public String getOfficeAreaCode() {
                  return officeAreaCode;
              }

              public void setOfficeAreaCode(String arg) {
                  officeAreaCode = arg;
              }

              public String getOfficeNumber() {
                  return officeNumber;
              }

              public void setOfficeNumber(String arg) {
                  officeNumber = arg;
              }
          }

          //新提煉的類(以對(duì)象替換數(shù)據(jù)值)
          public class TelephoneNumber {
              private String areaCode;
              private String number;

              public String getTelephnoeNumber() {
                  return ("(" + getAreaCode() + ")" + number);
              }

              String getAreaCode() {
                  return areaCode;
              }

              void setAreaCode(String arg) {
                  areaCode = arg;
              }

              String getNumber() {
                  return number;
              }

              void setNumber(String arg) {
                  number = arg;
              }
          }

          組合優(yōu)先于繼承

          繼承使實(shí)現(xiàn)代碼重用的有力手段,但這并非總是完成這項(xiàng)工作的最佳工具,使用不當(dāng)會(huì)導(dǎo)致軟件變得很脆弱。與方法調(diào)用不同的是,繼承打破了封裝性。子類依賴于其父類中特定功能的實(shí)現(xiàn)細(xì)節(jié),如果父類的實(shí)現(xiàn)隨著發(fā)行版本的不同而變化,子類可能會(huì)遭到破壞,即使他的代碼完全沒有改變。

          舉例說明,假設(shè)有一個(gè)程序使用HashSet,為了調(diào)優(yōu)該程序的性能,需要統(tǒng)計(jì)HashSet自從它創(chuàng)建以來添加了多少個(gè)元素。為了提供該功能,我們編寫一個(gè)HashSet的變體。

          // Inappropriate use of inheritance!
          public class InstrumentedHashSet<Eextends HashSet<E{
              // The number of attempted element insertions
              private int addCount = 0;

              public InstrumentedHashSet() { }

              public InstrumentedHashSet(int initCap, float loadFactor) {
                  super(initCap, loadFactor);
              }

              @Override
              public boolean add(E e) {
                  addCount++;
                  return super.add(e);
              }

              @Override
              public boolean addAll(Collection<? extends E> c) {
                  addCount += c.size();
                  return super.addAll(c);
              }

              public int getAddCount() {
                  return addCount;
              }
          }

          通過在新的類中增加一個(gè)私有域,它引用現(xiàn)有類的一個(gè)實(shí)例,這種設(shè)計(jì)被稱為組合,因?yàn)楝F(xiàn)有的類變成了新類的一個(gè)組件。這樣得到的類將會(huì)非常穩(wěn)固,它不依賴現(xiàn)有類的實(shí)現(xiàn)細(xì)節(jié)。即使現(xiàn)有的類添加了新的方法,也不會(huì)影響新的類。許多設(shè)計(jì)模式使用就是這種套路,比如代理模式、裝飾者模式

          // Reusable forwarding class
          public class ForwardingSet<Eimplements Set<E{
              private final Set<E> s;
              public ForwardingSet(Set<E> s) this.s = s; }

              @Override
              public int size() return s.size(); }
              @Override
              public boolean isEmpty() return s.isEmpty(); }
              @Override
              public boolean contains(Object o) return s.contains(o); }
              @Override
              public Iterator<E> iterator() return s.iterator(); }
              @Override
              public Object[] toArray() { return s.toArray(); }
              @Override
              public <T> T[] toArray(T[] a) { return s.toArray(a); }
              @Override
              public boolean add(E e) return s.add(e); }
              @Override
              public boolean remove(Object o) return s.remove(o); }
              @Override
              public boolean containsAll(Collection<?> c) return s.containsAll(c); }
              @Override
              public boolean addAll(Collection<? extends E> c) return s.addAll(c); }
              @Override
              public boolean retainAll(Collection<?> c) return s.retainAll(c); }
              @Override
              public boolean removeAll(Collection<?> c) return s.removeAll(c); }
              @Override
              public void clear() { s.clear(); }
          }

          // Wrappter class - uses composition in place of inheritance
          public class InstrumentedHashSet<Eextends ForwardingSet<E{
              private int addCount = 0;

              public InstrumentedHashSet1(Set<E> s) {
                  super(s);
              }

              @Override
              public boolean add(E e) {
                  addCount++;
                  return super.add(e);
              }

              @Override
              public boolean addAll(Collection<? extends E> c) {
                  addCount += c.size();
                  return super.addAll(c);
              }

              public int getAddCount() {
                  return addCount;
              }
          }

          繼承與組合如何取舍

          • 只有當(dāng)子類真正是父類的子類型時(shí),才適合繼承。對(duì)于兩個(gè)類A和B,只有兩者之間確實(shí)存在“is-a”關(guān)系的時(shí)候,類B才應(yīng)該繼承A;
          • 在包的內(nèi)部使用繼承是非常安全的,子類和父類的實(shí)現(xiàn)都處在同一個(gè)程序員的控制之下;
          • 對(duì)于專門為了繼承而設(shè)計(jì)并且具有很好的文檔說明的類來說,使用繼承也是非常安全的;
          • 其他情況就應(yīng)該優(yōu)先考慮組合的方式來實(shí)現(xiàn)

          接口優(yōu)于抽象類

          Java提供了兩種機(jī)制,可以用來定義允許多個(gè)實(shí)現(xiàn)的類型:接口和抽象類。自從Java8為接口增加缺省方法(default method),這兩種機(jī)制都允許為實(shí)例方法提供實(shí)現(xiàn)。主要區(qū)別在于,為了實(shí)現(xiàn)由抽象類定義的類型,類必須稱為抽象類的一個(gè)子類。因?yàn)镴ava只允許單繼承,所以用抽象類作為類型定義受到了限制。

          接口相比于抽象類的優(yōu)勢:

          • 現(xiàn)有的類可以很容易被更新,以實(shí)現(xiàn)新的接口。
          • 接口是定義混合類型(比如Comparable)的理想選擇。
          • 接口允許構(gòu)造非層次結(jié)構(gòu)的類型框架。

          接口雖然提供了缺省方法,但接口仍有有以下局限性:

          • 接口的變量修飾符只能是public static final的
          • 接口的方法修飾符只能是public的
          • 接口不存在構(gòu)造函數(shù),也不存在this
          • 可以給現(xiàn)有接口增加缺省方法,但不能確保這些方法在之前存在的實(shí)現(xiàn)中都能良好運(yùn)行。
          • 因?yàn)檫@些默認(rèn)方法是被注入到現(xiàn)有實(shí)現(xiàn)中的,它們的實(shí)現(xiàn)者并不知道,也沒有許可

          接口缺省方法的設(shè)計(jì)目的和優(yōu)勢在于:

          為了接口的演化

          • Java 8 之前我們知道,一個(gè)接口的所有方法其子類必須實(shí)現(xiàn)(當(dāng)然,這個(gè)子類不是一個(gè)抽象類),但是 java 8 之后接口的默認(rèn)方法可以選擇不實(shí)現(xiàn),如上的操作是可以通過編譯期編譯的。這樣就避免了由 Java 7 升級(jí)到 Java 8 時(shí)項(xiàng)目編譯報(bào)錯(cuò)了。Java8在核心集合接口中增加了許多新的缺省方法,主要是為了便于使用lambda。

          可以減少第三方工具類的創(chuàng)建

          • 例如在 List 等集合接口中都有一些默認(rèn)方法,List 接口中默認(rèn)提供 replaceAll(UnaryOperator)、sort(Comparator)、、spliterator()等默認(rèn)方法,這些方法在接口內(nèi)部創(chuàng)建,避免了為了這些方法而專門去創(chuàng)建相應(yīng)的工具類。

          可以避免創(chuàng)建基類

          • 在 Java 8 之前我們可能需要?jiǎng)?chuàng)建一個(gè)基類來實(shí)現(xiàn)代碼復(fù)用,而默認(rèn)方法的出現(xiàn),可以不必要去創(chuàng)建基類。

          由于接口的局限性和設(shè)計(jì)目的的不同,接口并不能完全替換抽象類。但是通過對(duì)接口提供一個(gè)抽象的骨架實(shí)現(xiàn)類,可以把接口和抽象類的優(yōu)點(diǎn)結(jié)合起來。 接口負(fù)責(zé)定義類型,或許還提供一些缺省方法,而骨架實(shí)現(xiàn)類則負(fù)責(zé)實(shí)現(xiàn)除基本類型接口方法之外,剩下的非基本類型接口方法。擴(kuò)展骨架實(shí)現(xiàn)占了實(shí)現(xiàn)接口之外的大部分工作。這就是模板方法(Template Method)設(shè)計(jì)模式。

          接口Protocol:定義了RPC協(xié)議層兩個(gè)主要的方法,export暴露服務(wù)和refer引用服務(wù)

          抽象類AbstractProtocol:封裝了暴露服務(wù)之后的Exporter和引用服務(wù)之后的Invoker實(shí)例,并實(shí)現(xiàn)了服務(wù)銷毀的邏輯

          具體實(shí)現(xiàn)類XxxProtocol:實(shí)現(xiàn)export暴露服務(wù)和refer引用服務(wù)具體邏輯

          優(yōu)先考慮泛型

          聲明中具有一個(gè)或者多個(gè)類型參數(shù)(type parameter)的類或者接口,就是泛型(generic)類或者接口。泛型類和接口統(tǒng)稱為泛型(generic type)。泛型從Java 5引入,提供了編譯時(shí)類型安全檢測機(jī)制。泛型的本質(zhì)是參數(shù)化類型,通過一個(gè)參數(shù)來表示所操作的數(shù)據(jù)類型,并且可以限制這個(gè)參數(shù)的類型范圍。泛型的好處就是編譯期類型檢測,避免類型轉(zhuǎn)換。

          // 比較三個(gè)值并返回最大值
          public static <T extends Comparable<T>> maximum(T x, T y, T z) {
            T max = x;
            // 假設(shè)x是初始最大值
            if ( y.compareTo( max ) > 0 ) {
              max = y; //y 更大
            }   if ( z.compareTo( max ) > 0 ) {
              max = z; // 現(xiàn)在 z 更大
            }   return max; // 返回最大對(duì)象
          }

          public static void main( String args[] ) {
            System.out.printf( "%d, %d 和 %d 中最大的數(shù)為 %d\n\n",  345, maximum( 345 ));
            System.out.printf( "%.1f, %.1f 和 %.1f 中最大的數(shù)為 %.1f\n\n",  6.68.87.7,  maximum( 6.68.87.7 ));
            System.out.printf( "%s, %s 和 %s 中最大的數(shù)為 %s\n","pear""apple""orange", maximum( "pear""apple""orange" ) );
          }

          不要使用原生態(tài)類型

          由于為了保持Java代碼的兼容性,支持和原生態(tài)類型轉(zhuǎn)換,并使用擦除機(jī)制實(shí)現(xiàn)的泛型。但是使用原生態(tài)類型就會(huì)失去泛型的優(yōu)勢,會(huì)受到編譯器警告。

          要盡可能地消除每一個(gè)非受檢警告

          每一條警告都表示可能在運(yùn)行時(shí)拋出ClassCastException異常。要盡最大的努力去消除這些警告。如果無法消除但是可以證明引起警告的代碼是安全的,就可以在盡可能小的范圍中,使用@SuppressWarnings("unchecked")注解來禁止警告,但是要把禁止的原因記錄下來。

          利用有限制通配符來提升API的靈活性

          參數(shù)化類型不支持協(xié)變的,即對(duì)于任何兩個(gè)不同的類型Type1和Type2而言,List既不是List的子類型,也不是它的超類。為了解決這個(gè)問題,提高靈活性,Java提供了一種特殊的參數(shù)化類型,稱作有限制的通配符類型,即List<? extends E>和List<? super E>。使用原則是producer-extends,consumer-super(PECS)。如果即是生產(chǎn)者,又是消費(fèi)者,就沒有必要使用通配符了。

          還有一種特殊的無限制通配符List<?>,表示某種類型但不確定。常用作泛型的引用,不可向其添加除Null以外的任何對(duì)象。

          //List<? extends E>
          // Number 可以認(rèn)為 是Number 的 "子類"
          List<? extends Number> numberArray = new ArrayList<Number>();
          // Integer 是 Number 的子類
          List<? extends Number> numberArray = new ArrayList<Integer>();
          // Double 是 Number 的子類
          List<? extends Number> numberArray = new ArrayList<Double>();

          //List<? super E>
          // Integer 可以認(rèn)為是 Integer 的 "父類"
          List<? super Integer> array = new ArrayList<Integer>();、
          // Number 是 Integer 的 父類
          List<? super Integer> array = new ArrayList<Number>();
          // Object 是 Integer 的 父類
          List<? super Integer> array = new ArrayList<Object>();

          public static <T> void copy(List<? super T> dest, List<? extends T> src) {
            int srcSize = src.size();
            if (srcSize > dest.size())
             throw new IndexOutOfBoundsException("Source does not fit in dest");
            if (srcSize < COPY_THRESHOLD || (src instanceof RandomAccess && dest instanceof RandomAccess)) {
              for (int i=0; i<srcSize; i++)
              dest.set(i, src.get(i));
            } else {
              ListIterator<? super T> di=dest.listIterator();
              ListIterator<? extends T> si=src.listIterator();
              for (int i=0; i<srcSize; i++) {
                di.next();
                di.set(si.next());
              }
            }
          }

          靜態(tài)成員類優(yōu)于非靜態(tài)成員類

          嵌套類(nested class)是指定義在另一個(gè)類的內(nèi)部的類。嵌套類存在的目的只是為了它的外部類提供服務(wù),如果其他的環(huán)境也會(huì)用到的話,應(yīng)該成為一個(gè)頂層類(top-level class)。 嵌套類有四種:靜態(tài)成員類(static member class)、非靜態(tài)成員類(nonstatic member class)、匿名類(anonymous class)和 局部類(local class)。除了第一種之外,其他三種都稱為內(nèi)部類(inner class)。

          匿名類(anonymous class)

          沒有名字,聲明的同時(shí)進(jìn)行實(shí)例化,只能使用一次。當(dāng)出現(xiàn)在非靜態(tài)的環(huán)境中,會(huì)持有外部類實(shí)例的引用。通常用于創(chuàng)建函數(shù)對(duì)象和過程對(duì)象,不過現(xiàn)在會(huì)優(yōu)先考慮lambda。

          局部類(local class)

          任何可以聲明局部變量的地方都可以聲明局部類,同時(shí)遵循同樣的作用域規(guī)則。跟匿名類不同的是,有名字可以重復(fù)使用。不過實(shí)際很少使用局部類。

          靜態(tài)成員類(static member class)

          最簡單的一種嵌套類,聲明在另一個(gè)類的內(nèi)部,是這個(gè)類的靜態(tài)成員,遵循同樣的可訪問性規(guī)則。常見的用法是作為公有的輔助類,只有與它的外部類一起使用才有意義。

          非靜態(tài)成員類(nonstatic member class)

          盡管語法上,跟靜態(tài)成員類的唯一區(qū)別就是類的聲明不包含static,但兩者有很大的不同。非靜態(tài)成員類的每個(gè)實(shí)例都隱含地與外部類的實(shí)例相關(guān)聯(lián),可以訪問外部類的成員屬性和方法。另外必須先創(chuàng)建外部類的實(shí)例之后才能創(chuàng)建非靜態(tài)成員類的實(shí)例。

          總而言之,這四種嵌套類都有自己的用途。假設(shè)這個(gè)嵌套類屬于一個(gè)方法的內(nèi)部,如果只需要在一個(gè)地方創(chuàng)建實(shí)例,并且已經(jīng)有了一個(gè)預(yù)置的類型可以說明這個(gè)類的特征,就要把它做成匿名類。如果一個(gè)嵌套類需要在單個(gè)方法之外仍然可見,或者它太長了,不適合放在方法內(nèi)部,就應(yīng)該使用成員類。如果成員類的每個(gè)實(shí)例都需要一個(gè)指向其外圍實(shí)例的引用,就要把成員類做成非靜態(tài)的,否則就做成靜態(tài)的。

          優(yōu)先使用模板/工具類

          通過對(duì)常見場景的代碼邏輯進(jìn)行抽象封裝,形成相應(yīng)的模板工具類,可以大大減少重復(fù)代碼,專注于業(yè)務(wù)邏輯,提高代碼質(zhì)量。

          分離對(duì)象的創(chuàng)建與使用

          面向?qū)ο缶幊滔鄬?duì)于面向過程,多了實(shí)例化這一步,而對(duì)象的創(chuàng)建必須要指定具體類型。我們常見的做法是“哪里用到,就在哪里創(chuàng)建”,使用實(shí)例和創(chuàng)建實(shí)例的是同一段代碼。這似乎使代碼更具有可讀性,但是某些情況下造成了不必要的耦合。

          public class BusinessObject {
           public void actionMethond {
               //Other things
               Service myServiceObj = new Service();
                 myServiceObj.doService();
                 //Other things
              }
          }

          public class BusinessObject {
           public void actionMethond {
               //Other things
               Service myServiceObj = new ServiceImpl();
                 myServiceObj.doService();
                 //Other things
              }
          }

          public class BusinessObject {
             private Service myServiceObj;
             public BusinessObject(Service aService) {
                 myServiceObj = aService;
              }
           public void actionMethond {
               //Other things
                 myServiceObj.doService();
                 //Other things
              }
          }

          public class BusinessObject {
             private Service myServiceObj;
             public BusinessObject() {
                 myServiceObj = ServiceFactory;
              }
           public void actionMethond {
               //Other things
                 myServiceObj.doService();
                 //Other things
              }
          }

          對(duì)象的創(chuàng)建者耦合的是對(duì)象的具體類型,而對(duì)象的使用者耦合的是對(duì)象的接口。也就是說,創(chuàng)建者關(guān)心的是這個(gè)對(duì)象是什么,而使用者關(guān)心的是它能干什么。這兩者應(yīng)該視為獨(dú)立的考量,它們往往會(huì)因?yàn)椴煌脑蚨淖儭?/p>

          當(dāng)對(duì)象的類型涉及多態(tài)、對(duì)象創(chuàng)建復(fù)雜(依賴較多)可以考慮將對(duì)象的創(chuàng)建過程分離出來,使得使用者不用關(guān)注對(duì)象的創(chuàng)建細(xì)節(jié)。設(shè)計(jì)模式中創(chuàng)建型模式的出發(fā)點(diǎn)就是如此,實(shí)際項(xiàng)目中可以使用工廠模式、構(gòu)建器、依賴注入的方式。

          可訪問性最小化

          區(qū)分一個(gè)組件設(shè)計(jì)得好不好,一個(gè)很重要的因素在于,它對(duì)于外部組件而言,是否隱藏了其內(nèi)部數(shù)據(jù)和實(shí)現(xiàn)細(xì)節(jié)。Java提供了訪問控制機(jī)制來決定類、接口和成員的可訪問性。實(shí)體的可訪問性由該實(shí)體聲明所在的位置,以及該實(shí)體聲明中所出現(xiàn)的訪問修飾符(private、protected、public)共同決定的。

          對(duì)于頂層的(非嵌套的)類和接口,只有兩種的訪問級(jí)別:包級(jí)私有的(沒有public修飾)和公有的(public修飾)。

          對(duì)于成員(實(shí)例/域、方法、嵌套類和嵌套接口)由四種的訪問級(jí)別,可訪問性如下遞增:

          • 私有的(private修飾)--只有在聲明該成員的頂層類內(nèi)部才可以訪問這個(gè)成員;
          • 包級(jí)私有的(默認(rèn))--聲明該成員的包內(nèi)部的任何類都可以訪問這個(gè)成員;
          • 受保護(hù)的(protected修飾)--聲明該成員的類的子類可以訪問這個(gè)成員,并且聲明該成員的包內(nèi)部的任何類也可以訪問這個(gè)成員;
          • 公有的(public修飾)--在任何地方都可以訪問該成員;

          正確地使用這些修飾符對(duì)于實(shí)現(xiàn)信息隱藏是非常關(guān)鍵的,原則就是:盡可能地使每個(gè)類和成員不被外界訪問(私有或包級(jí)私有)。這樣好處就是在以后的發(fā)行版本中,可以對(duì)它進(jìn)行修改、替換或者刪除,而無須擔(dān)心會(huì)影響現(xiàn)有的客戶端程序。

          • 如果類或接口能夠做成包級(jí)私有的,它就應(yīng)該被做成包級(jí)私有的;
          • 如果一個(gè)包級(jí)私有的頂層類或接口只是在某一個(gè)類的內(nèi)部被用到,就應(yīng)該考慮使它成為那個(gè)類的私有嵌套類;
          • 公有類不應(yīng)直接暴露實(shí)例域,應(yīng)該提供相應(yīng)的方法以保留將來改變?cè)擃惖膬?nèi)部表示法的靈活性;
          • 當(dāng)確定了類的公有API之后,應(yīng)該把其他的成員都變成私有的;
          • 如果同一個(gè)包下的類之間存在比較多的訪問時(shí),就要考慮重新設(shè)計(jì)以減少這種耦合;

          可變性最小化

          不可變類是指其實(shí)例不能被修改的類。每個(gè)實(shí)例中包含的所有信息都必須在創(chuàng)建該實(shí)例時(shí)提供,并在對(duì)象的整個(gè)生命周期內(nèi)固定不變。不可變類好處就是簡單易用、線程安全、可自由共享而不容易出錯(cuò)。Java平臺(tái)類庫中包含許多不可變的類,比如String、基本類型包裝類、BigDecimal等。

          為了使類成為不可變,要遵循下面五條規(guī)則:

          • 聲明所有的域都是私有的

          • 聲明所有的域都是final的


            • 如果一個(gè)指向新創(chuàng)建實(shí)例的引用在缺乏同步機(jī)制的情況下,從一個(gè)線程被傳遞到另一個(gè)線程,就必須確保正確的行為
          • 不提供任何會(huì)修改對(duì)象狀態(tài)的方法

          • 保證類不會(huì)被擴(kuò)展(防止子類化,類聲明為final)


            • 防止粗心或者惡意的子類假裝對(duì)象的狀態(tài)已經(jīng)改變,從而破壞該類的不可變行為
          • 確保對(duì)任何可變組件的互斥訪問


            • 如果類具有指向可變對(duì)象的域,則必須確保該類的客戶端無法獲得指向這些對(duì)象的引用。并且,永遠(yuǎn)不要用客戶端提供的對(duì)象引用來初始化這樣的域,也不要從任何訪問方法中返回該對(duì)象引用。在構(gòu)造器、訪問方法和readObject 方法中使用保護(hù)性拷貝技術(shù)

          可變性最小化的一些建議:

          • 除非有很好的理由要讓類成為可變的類,否則它就應(yīng)該是不可變的;
          • 如果類不能被做成不可變的,仍然應(yīng)該盡可能地限制它的可變性;
          • 除非有令人信服的理由要使域變成非final的,否則要使每個(gè)域都是private final的;
          • 構(gòu)造器應(yīng)該創(chuàng)建完全初始化的對(duì)象,并建立起所有的約束關(guān)系;

          質(zhì)量如何保證

          測試驅(qū)動(dòng)開發(fā)

          測試驅(qū)動(dòng)開發(fā)(TDD)要求以測試作為開發(fā)過程的中心,要求在編寫任何代碼之前,首先編寫用于產(chǎn)碼行為的測試,而編寫的代碼又要以使測試通過為目標(biāo)。TDD要求測試可以完全自動(dòng)化地運(yùn)行,并在對(duì)代碼重構(gòu)前后必須運(yùn)行測試。

          TDD的最終目標(biāo)是整潔可用的代碼(clean code that works)。大多數(shù)的開發(fā)者大部分時(shí)間無法得到整潔可用的代碼。辦法是分而治之。首先解決目標(biāo)中的“可用”問題,然后再解決“代碼的整潔”問題。這與體系結(jié)構(gòu)驅(qū)動(dòng)(architecture-driven)的開發(fā)相反。

          采用TDD另一個(gè)好處就是讓我們擁有一套伴隨代碼產(chǎn)生的詳盡的自動(dòng)化測試集。將來無論出于任何原因(需求、重構(gòu)、性能改進(jìn))需要對(duì)代碼進(jìn)行維護(hù)時(shí),在這套測試集的驅(qū)動(dòng)下工作,我們代碼將會(huì)一直是健壯的。

          TDD的開發(fā)周期

          添加一個(gè)測試 -> 運(yùn)行所有測試并檢查測試結(jié)果 -> 編寫代碼以通過測試 -> 運(yùn)行所有測試且全部通過 -> 重構(gòu)代碼,以消除重復(fù)設(shè)計(jì),優(yōu)化設(shè)計(jì)結(jié)構(gòu)

          兩個(gè)基本的原則

          • 僅在測試失敗時(shí)才編寫代碼并且只編寫剛好使測試通過的代碼
          • 編寫下一個(gè)測試之前消除現(xiàn)有的重復(fù)設(shè)計(jì),優(yōu)化設(shè)計(jì)結(jié)構(gòu)

          關(guān)注點(diǎn)分離是這兩條規(guī)則隱含的另一個(gè)非常重要的原則。其表達(dá)的含義指在編碼階段先達(dá)到代碼“可用”的目標(biāo),在重構(gòu)階段再追求“整潔”目標(biāo),每次只關(guān)注一件事!

          分層測試點(diǎn)

          往期推薦

          微信“炸屎”功能,被玩壞了。。

          常用正則表達(dá)式最強(qiáng)整理(速查手冊(cè))

          快來搶紅包!

          什么是數(shù)據(jù)湖?為什么要數(shù)據(jù)湖?如何建湖?如何ETL?

          下方二維碼關(guān)注我

          技術(shù)草根堅(jiān)持分享 編程,算法,架構(gòu)

          看完文章,餓了點(diǎn)外賣,點(diǎn)擊 ??《無門檻外賣優(yōu)惠券,每天免費(fèi)領(lǐng)!》

          朋友,助攻一把!點(diǎn)個(gè)在看
          瀏覽 35
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久高清一区二区三区 | 99九九国产毛片 | 91超碰影院青青草人人 | 亚洲综合日韩在线 | 精品人人妻|