【設(shè)計模式】規(guī)范與重構(gòu)

1. 重構(gòu)的目的倦始?

重構(gòu)是一種對軟件內(nèi)部結(jié)構(gòu)的改善括授,目的是在不改變軟件的可見行為的情況下,使其更易理解踏拜,修改成本更低碎赢。

1.1 重構(gòu)不改變軟件的可見行為

也就是在保證功能不變的前提下,利用設(shè)計思想速梗、原則揩抡、模式編程規(guī)范等理論來優(yōu)化代碼,修改設(shè)計上的不足镀琉,提高代碼質(zhì)量峦嗤。

2. 為什么要重構(gòu)

  1. 重構(gòu)是保證代碼質(zhì)量的有效手段
  2. 重構(gòu)是避免前期過度設(shè)計的有效手段
  3. 重構(gòu)可以提供工程師的代碼能力

2.1 重構(gòu)對于工程師能力提升的重要性

初級工程師在維護(hù)代碼,高級工程師在寫代碼屋摔,資深工程師在重構(gòu)代碼烁设。

意思是初級工程師在原有的代碼上修改 bug,增加或修改功能钓试。高級工程師從零開始設(shè)計代碼結(jié)構(gòu)装黑、搭建代碼框架;而資深工程師為代碼質(zhì)量負(fù)責(zé)弓熏,需要發(fā)覺代碼存在的問題恋谭。

3. 重構(gòu)的對象

根據(jù)重構(gòu)的規(guī)模,分為大規(guī)模高層次重構(gòu)和小規(guī)劃低層次重構(gòu)挽鞠。

大型重構(gòu)指的是:對頂層代碼的重構(gòu)疚颊,包括系統(tǒng)、模塊信认、代碼結(jié)構(gòu)及類與類之間的關(guān)系等材义。常用的手段有:分層、模塊化嫁赏、解耦其掂、抽象可復(fù)用組件等。重構(gòu)的工具常用的有:設(shè)計思想潦蝇、原則和設(shè)計模式款熬。

小型重構(gòu)指的是:對代碼細(xì)節(jié)的重構(gòu)深寥,主要針對類、函數(shù)贤牛、變量等代碼級別的重構(gòu)翩迈。常見的有:規(guī)范命名、規(guī)范注釋盔夜、消除超大類或函數(shù)负饲、提取重復(fù)代碼等。常用的工具有編碼規(guī)范喂链。

4. 重構(gòu)的時機(jī):什么時候重構(gòu)

一般的重構(gòu)策略是持續(xù)重構(gòu)返十。平時事情不多的時候,就看看代碼有哪些不好的地方椭微,優(yōu)化一下洞坑。或者在修改蝇率,添加某個功能的時候迟杂,順便把存在問題的代碼重構(gòu)一下。

5. 重構(gòu)的方法

5.1 大型重構(gòu)的方法

對于大型重構(gòu)而言本慕,需要分階段進(jìn)行排拷。每個階段完成一小部分的代碼重構(gòu),然后锅尘,提交监氢、測試和運行。如果沒有問題后藤违,再進(jìn)行下一階段的重構(gòu)浪腐。

5.2 小型重構(gòu)的方法

由于小型重構(gòu)往往影響較小,改動耗時較短顿乒,所以议街,只要你愿意,什么時候都可以進(jìn)行重構(gòu)璧榄。

5.3 重構(gòu)的負(fù)責(zé)人

常常需要資深的工程師特漩,項目 Leader 來負(fù)責(zé)。

6. 單元測試

6.1 什么單元測試

集成測試

集成測試的測試對象是整個系統(tǒng)或者某個功能模塊犹菱。比如:用戶注冊拾稳、登錄模塊吮炕。

單元測試

單元測試的測試對象是類或者函數(shù)腊脱,用來測試一個類和函數(shù)是否都按照預(yù)期邏輯執(zhí)行。

6.2 單元測試的作用

  1. 單元測試能有效地幫你發(fā)現(xiàn)代碼中的 bug龙亲,寫出 bug free 的代碼
  2. 寫單元測試能幫你發(fā)現(xiàn)代碼設(shè)計上的問題陕凹,代碼的可測試性是評判代碼質(zhì)量的一個重要標(biāo)準(zhǔn)
  3. 單元測試的對集成測試的補(bǔ)充悍抑,集成測試往往無法覆蓋代碼實現(xiàn)的方方面面
  4. 寫單元測試的過程本身就是代碼重構(gòu)的過程,在寫單元測試的過程中杜耙,就相當(dāng)于是對代碼的一次 Code View
  5. 閱讀單元測試能幫助你快速熟悉代碼
  6. 單元測試是 TDD 可落地執(zhí)行的改進(jìn)方案搜骡,先寫代碼,緊接著寫單元測試佑女,最后根據(jù)單元測試反饋出來問題记靡,再回頭對代碼進(jìn)行重構(gòu)

6.3 單元測試覆蓋率存在問題

單元測試覆蓋率常常基于所有方法覆蓋測試的百分比來計算的团驱。

而在代碼編寫過程中摸吠,并不是所有方法都需要被覆蓋的,比如:get/set嚎花,而實現(xiàn)應(yīng)該關(guān)注的是:需要添加單元測試的類或函數(shù)的測試是否足夠全面寸痢,是否覆蓋了各種輸入、異常紊选、邊界條件等測試用例

6.4 單元測試不需要了解代碼的實現(xiàn)邏輯

單元測試不需要依賴被測試函數(shù)的具體實現(xiàn)邏輯啼止,它只關(guān)心被測試函數(shù)實現(xiàn)了什么功能。

6.5 Google 內(nèi)部對待單元測試的態(tài)度

很多項目幾乎沒有測試團(tuán)隊參與兵罢,代碼的正確性完全靠開發(fā)團(tuán)隊來保障献烦。

7. 代碼的可測試性

什么是代碼的可測試性?

所謂代碼的可測試性卖词,就是針對代碼編寫單元測試的難易程度仿荆。

7.1 單元測試改造前代碼

public class Transaction {
  private String id;
  private Long buyerId;
  private Long sellerId;
  private Long productId;
  private String orderId;
  private Long createTimestamp;
  private Double amount;
  private STATUS status;
  private String walletTransactionId;
  
  // ...get() methods...
  
  public Transaction(String preAssignedId, Long buyerId, Long sellerId, Long productId, String orderId) {
    if (preAssignedId != null && !preAssignedId.isEmpty()) {
      this.id = preAssignedId;
    } else {
      this.id = IdGenerator.generateTransactionId();
    }
    if (!this.id.startWith("t_")) {
      this.id = "t_" + preAssignedId;
    }
    this.buyerId = buyerId;
    this.sellerId = sellerId;
    this.productId = productId;
    this.orderId = orderId;
    this.status = STATUS.TO_BE_EXECUTD;
    this.createTimestamp = System.currentTimestamp();
  }
  
  public boolean execute() throws InvalidTransactionException {
    if ((buyerId == null || (sellerId == null || amount < 0.0) {
      throw new InvalidTransactionException(...);
    }
    if (status == STATUS.EXECUTED) return true;
    boolean isLocked = false;
    try {
      isLocked = RedisDistributedLock.getSingletonIntance().lockTransction(id);
      if (!isLocked) {
        return false; // 鎖定未成功,返回false坏平,job兜底執(zhí)行
      }
      if (status == STATUS.EXECUTED) return true; // double check
      long executionInvokedTimestamp = System.currentTimestamp();
      if (executionInvokedTimestamp - createdTimestap > 14days) {
        this.status = STATUS.EXPIRED;
        return false;
      }
      WalletRpcService walletRpcService = new WalletRpcService();
      String walletTransactionId = walletRpcService.moveMoney(id, buyerId, sellerId, amount);
      if (walletTransactionId != null) {
        this.walletTransactionId = walletTransactionId;
        this.status = STATUS.EXECUTED;
        return true;
      } else {
        this.status = STATUS.FAILED;
        return false;
      }
    } finally {
      if (isLocked) {
       RedisDistributedLock.getSingletonIntance().unlockTransction(id);
      }
    }
  }
}

該代碼需要包含以下測試用例:

  1. 正常情況下拢操,交易執(zhí)行成功,交易狀態(tài)設(shè)置為 EXECUTED舶替,函數(shù)返回成功
  2. buyId令境、sellId 為 null,amount 小于 0顾瞪,返回 InvalidTransactionException
  3. 交易已過期舔庶,交易狀態(tài)為 EXPIRED,返回 false
  4. 交易已經(jīng)執(zhí)行了陈醒,不再重復(fù)執(zhí)行惕橙,返回 true
  5. 錢包轉(zhuǎn)錢失敗,交易狀態(tài)為 FAILED钉跷,函數(shù)返回 false
  6. 交易正在執(zhí)行弥鹦,不會被重復(fù)執(zhí)行,返回 false

7.2 測試用例 1

單元測試主要是測試程序員自己寫的代碼邏輯是否存在問題,并不需要測試所依賴系統(tǒng)或服務(wù)邏輯的正確性彬坏。所以朦促,如果代碼中依賴了外部系統(tǒng)或者不可控組件,如:數(shù)據(jù)庫栓始,網(wǎng)絡(luò)服務(wù)和文件系統(tǒng)等务冕,就需要將被測試代碼與外部系統(tǒng)解依賴,解依賴的方法就是 mock幻赚。

1. 使用 mock 替換 WalletRpcService 服務(wù)

對于上述代碼中的 WalletRpcService 服務(wù)禀忆,需要將其 mock,具體做法為:

  1. 自定義 mock 類繼承 WalletRpcService 類
public class MockWalletRpcServiceOne extends WalletRpcService {
  public String moveMoney(Long id, Long fromUserId, Long toUserId, Double amount) {
    return "123bac";
  } 
}

public class MockWalletRpcServiceTwo extends WalletRpcService {
  public String moveMoney(Long id, Long fromUserId, Long toUserId, Double amount) {
    return null;
  } 
}
  1. excute() 方法進(jìn)行重構(gòu)落恼,通過依賴注入的方式引入 WalletRpcService 服務(wù)
public class Transaction {
  //...
  // 添加一個成員變量及其set方法
  private WalletRpcService walletRpcService;
  
  public void setWalletRpcService(WalletRpcService walletRpcService) {
    this.walletRpcService = walletRpcService;
  }
  // ...
  public boolean execute() {
    // ...
    // 刪除下面這一行代碼
    // WalletRpcService walletRpcService = new WalletRpcService();
    // ...
  }
}
  1. 使用 mock 替換 WalletRpcService 服務(wù)
public void testExecute() {
  Long buyerId = 123L;
  Long sellerId = 234L;
  Long productId = 345L;
  Long orderId = 456L;
  Transction transaction = new Transaction(null, buyerId, sellerId, productId, orderId);
  // 使用mock對象來替代真正的RPC服務(wù)
  transaction.setWalletRpcService(new MockWalletRpcServiceOne()):
  boolean executedResult = transaction.execute();
  assertTrue(executedResult);
  assertEquals(STATUS.EXECUTED, transaction.getStatus());
}

2. 使用 mock 替換 RedisDistributedLock

mock 單例類存在的問題:單例類相當(dāng)于一個全局變量油湖,我們無法 mock(無法繼承和重寫方法),也無法通過依賴注入的方式來替換领跛。

  1. 將單例類重新使用普通類封裝
public class TransactionLock {
  public boolean lock(String id) {
    return RedisDistributedLock.getSingletonIntance().lockTransction(id);
  }
  
  public void unlock() {
    RedisDistributedLock.getSingletonIntance().unlockTransction(id);
  }
}
  1. 將 RedisDistributedLock 重構(gòu)為通過依賴注入引入
public class Transaction {
  //...
  private TransactionLock lock;
  
  public void setTransactionLock(TransactionLock lock) {
    this.lock = lock;
  }
 
  public boolean execute() {
    //...
    try {
      isLocked = lock.lock();
      //...
    } finally {
      if (isLocked) {
        lock.unlock();
      }
    }
    //...
  }
}
  1. 創(chuàng)建 TransactionLock 的 mock 對象乏德,并復(fù)寫其真實方法,返回我們想要的任何結(jié)果
public void testExecute() {
  Long buyerId = 123L;
  Long sellerId = 234L;
  Long productId = 345L;
  Long orderId = 456L;
  
  TransactionLock mockLock = new TransactionLock() {
    public boolean lock(String id) {
      return true;
    }
  
    public void unlock() {}
  };
  
  Transction transaction = new Transaction(null, buyerId, sellerId, productId, orderId);
  transaction.setWalletRpcService(new MockWalletRpcServiceOne());
  transaction.setTransactionLock(mockLock);
  boolean executedResult = transaction.execute();
  assertTrue(executedResult);
  assertEquals(STATUS.EXECUTED, transaction.getStatus());
}

mock 是什么

所謂 mock 就是用一個“假”的服務(wù)替換掉真的服務(wù)吠昭,由于 mock 的服務(wù)完全在我們的控制之下喊括,所以,完全可以模擬輸出我們想要的數(shù)據(jù)矢棚。

7.3 測試用例 3

public void testExecute_with_TransactionIsExpired() {
  Long buyerId = 123L;
  Long sellerId = 234L;
  Long productId = 345L;
  Long orderId = 456L;
  Transction transaction = new Transaction(null, buyerId, sellerId, productId, orderId);
  transaction.setCreatedTimestamp(System.currentTimestamp() - 14days);
  boolean actualResult = transaction.execute();
  assertFalse(actualResult);
  assertEquals(STATUS.EXPIRED, transaction.getStatus());
}

上面的代碼來寫單元測試沒有問題郑什,但是如果 setCreatedTimestamp() 沒有提供,而是在構(gòu)造函數(shù)中自動生成的蒲肋,該如何完成單元測試呢蘑拯?這同樣的是在寫單元測試過程中一類常見的問題,就是代碼中包含時間有關(guān)的“未決行為”邏輯兜粘。

解決方法:將這種未決行為的行為邏輯重新封裝申窘。上面的代碼中,我們需要把原有代碼的實現(xiàn)進(jìn)行重構(gòu)孔轴,把將交易上否過期的邏輯剃法,封裝到 isExpired() 函數(shù)即可。

  1. 封裝時間過期的邏輯
public class Transaction {

  protected boolean isExpired() {
    long executionInvokedTimestamp = System.currentTimestamp();
    return executionInvokedTimestamp - createdTimestamp > 14days;
  }
  
  public boolean execute() throws InvalidTransactionException {
    //...
      if (isExpired()) {
        this.status = STATUS.EXPIRED;
        return false;
      }
    //...
  }
}
  1. 創(chuàng)建 Transaction 對象路鹰,并復(fù)寫 isExpired() 方法
public void testExecute_with_TransactionIsExpired() {
  Long buyerId = 123L;
  Long sellerId = 234L;
  Long productId = 345L;
  Long orderId = 456L;
  Transction transaction = new Transaction(null, buyerId, sellerId, productId, orderId) {
    protected boolean isExpired() {
      return true;
    }
  };
  boolean actualResult = transaction.execute();
  assertFalse(actualResult);
  assertEquals(STATUS.EXPIRED, transaction.getStatus());
}

7.4 測試方法總結(jié)

1. 對普通對象進(jìn)行 mock 的方法

  1. 繼承原對象贷洲,并復(fù)寫對應(yīng)的方法,得到我們想要的任何結(jié)果
  2. 將原對象的依賴關(guān)系改為依賴注入的方式進(jìn)行依賴
  3. 在單元測試代碼中晋柱,將 mock 對象通過依賴注入的方式注入到類中

2. 對單例類進(jìn)行 mock 的方法

  1. 將單例類提供的功能优构,通過新的普通類進(jìn)行封裝
  2. 創(chuàng)建 1 中的普通類的實例對象,并復(fù)寫對應(yīng)的方法雁竞,得到我們想要的任何結(jié)果
  3. 對原有代碼進(jìn)行重構(gòu)钦椭,將直接依賴單例的地方,改為依賴封裝了單例類的普通對象;并通過依賴注入的方式依賴此普通對象
  4. 在單元測試代碼中玉凯,創(chuàng)建普通對象的實現(xiàn)势腮,并將其通過依賴注入的方式注入到類中

3. 針對時間等未決行為的處理方法

  1. 將未決進(jìn)行在原代碼中進(jìn)行重構(gòu)联贩,將其通過單獨的方法封裝
  2. 在單元測試代碼中漫仆,創(chuàng)建待測試對象時,復(fù)寫未決行為方法泪幌,返回我們想要的任何結(jié)果

7.5 常見的測試性不好的代碼

1. 未決行為

所謂未決行為就是代碼輸出是隨機(jī)或者說不確定的盲厌。比如和時間、隨機(jī)數(shù)有關(guān)的代碼祸泪。

2. 全局變量

public class RangeLimiter {
  private static AtomicInteger position = new AtomicInteger(0);
  public static final int MAX_LIMIT = 5;
  public static final int MIN_LIMIT = -5;

  public boolean move(int delta) {
    int currentPos = position.addAndGet(delta);
    boolean betweenRange = (currentPos <= MAX_LIMIT) && (currentPos >= MIN_LIMIT);
    return betweenRange;
  }
}

public class RangeLimiterTest {
  public void testMove_betweenRange() {
    RangeLimiter rangeLimiter = new RangeLimiter();
    assertTrue(rangeLimiter.move(1));
    assertTrue(rangeLimiter.move(3));
    assertTrue(rangeLimiter.move(-5));
  }

  public void testMove_exceedRange() {
    RangeLimiter rangeLimiter = new RangeLimiter();
    assertFalse(rangeLimiter.move(6));
  }
}

如果上面的 testMove_betweenRangetestMove_exceedRange 順序執(zhí)行吗浩,由于全局變量的存在,testMove_betweenRange 方法執(zhí)行后没隘,posion 的值一直存在懂扼,而導(dǎo)致 testMove_exceedRange 方法執(zhí)行斷言失敗。

3. 靜態(tài)方法

主要原因是靜態(tài)方法很難 mock右蒲。當(dāng)然阀湿,需要分情況來看,只有靜態(tài)方法耗時太長瑰妄、依賴外部資源陷嘴、邏輯復(fù)雜、存在未決行為等的情況下间坐,我們才需要在單元測試中對其進(jìn)行 mock 操作灾挨。如果只是簡單的靜態(tài)方法,如:math.abs()竹宋,并不需要對其進(jìn)行 mock劳澄。

4. 復(fù)雜繼承

如果在父類中使用了外部對象,需要對其進(jìn)行 mock 后才能運行單元測試蜈七,那么所有子類在編寫單元測試的時候都需要 mock 這個依賴對象浴骂。

如果繼承關(guān)系過于復(fù)雜,越是底層的子類宪潮,需要 mock 的依賴類就越多溯警。

如果繼承關(guān)系比較復(fù)雜的情況下,需要通過組合狡相、接口和委托的方式對其進(jìn)行重構(gòu)梯轻。

5. 高耦合代碼

如果一個類的職責(zé)很重,需要依賴十幾個外部對象才能工作尽棕,在編寫單元測試的時候喳挑,可能就需要編寫十幾個 mock 對象,這顯然會大大增加編寫單元測試的成本。

8. 如何給代碼解耦

解耦的作用:是控制代碼復(fù)雜度的有效手段伊诵,利用解耦的方法對代碼重構(gòu)单绑,就是保證代碼不至于復(fù)雜到無法控制的有效手段。

8.1 封裝和抽象

通過封裝和抽象曹宴,可以有效地隱藏實現(xiàn)的復(fù)雜性搂橙,隔離實現(xiàn)的易變性,給依賴的模塊提供穩(wěn)定且易用的抽象接口笛坦。

8.2 引入中間層

讓兩兩之間存在依賴關(guān)系中的多個類区转,利用中介者設(shè)計模式,讓其共同依賴同一個中介類版扩,來降低類之間依賴的復(fù)雜性废离。

同時,在重構(gòu)的過程中礁芦,引入中間層可以起到過渡的作用蜻韭,能夠讓開發(fā)和重構(gòu)同步執(zhí)行,不互相干擾柿扣。當(dāng)某個接口開發(fā)設(shè)計得有問題肖方,我們需要修改它的定義兑障,同時钱豁,所有調(diào)用它的地方都要有相應(yīng)的改動。如果新開發(fā)的代碼也用到了這個接口怪蔑,那開發(fā)和重構(gòu)就沖突了娩践。為了讓重構(gòu)能夠小步快跑活翩,可以分四個階段來完成上述接口的修改:

  1. 引入一個中間層,包裹老的接口翻伺,同時提供新的接口定義
  2. 新開發(fā)的代碼依賴中間層提供的新接口
  3. 將依賴?yán)辖涌诘拇a改成依賴新的接口
  4. 確保所有的代碼都依賴新接口后材泄,刪除老接口

8.3 模塊化

  1. 對于一個復(fù)雜的系統(tǒng)來說,將系統(tǒng)劃分成各個獨立的模塊吨岭,讓不同的人復(fù)雜不同的模塊拉宗,即使在不了解全部實現(xiàn)細(xì)節(jié)的情況下,管理者也可能協(xié)調(diào)各個模塊辣辫,讓系統(tǒng)穩(wěn)定運轉(zhuǎn)
  2. 對于軟件開發(fā)來說旦事,不同的模塊之間通過 API 來進(jìn)行通信,每個模塊之間耦合很小急灭,每個小團(tuán)隊只需要聚焦于一個獨立的高內(nèi)聚模塊來開發(fā)
  3. 對于代碼層面來說姐浮,合理地劃分模塊能有效地解耦代碼,提高代碼的可讀性和可維護(hù)性葬馋。

8.4 單一職責(zé)原則

模塊或類的職責(zé)設(shè)計單一卖鲤,而不是大而全肾扰,依賴它的類或者它依賴的類就會比較少,代碼耦合也就相應(yīng)的降低了蛋逾。

8.5 基于接口而非實現(xiàn)編程

通過接口這個中間層集晚,隔離變化和具體的實現(xiàn)。在有依賴關(guān)系的模塊或類之間区匣,一方的改變偷拔,不會影響到另一方。

8.6 依賴注入

依賴注入是將代碼之間的強(qiáng)耦合變?yōu)槿躐詈铣了蹋M管依賴注入無法將本來有依賴關(guān)系的兩個類解耦為沒有依賴關(guān)系条摸,但可以讓依賴關(guān)系變得沒有那么緊密悦污,容易做到插拔铸屉。

8.7 多用組合少用繼承

和依賴注入類似,或者說組合就是依賴注入的一種具體實現(xiàn)方式切端。通過組合讓原本強(qiáng)耦合關(guān)系并成一種弱耦合關(guān)系彻坛,同樣的,也容易做到插拔踏枣。

8.8 迪米特原則

不該有依賴關(guān)系的類之間不要有依賴昌屉;有依賴關(guān)系的兩個類之間,盡量只依賴其必要的接口茵瀑。明顯看出间驮,這也是一種降低類之間耦合度的一種方式。

9. 編程規(guī)范

9.1 命名

  1. 名字太長马昨,由于代碼列長度有限制的情況下竞帽,就會經(jīng)常出現(xiàn)一條語句被分割成兩行的情況,這其實會影響代碼可讀性鸿捧。在能達(dá)意的情況下屹篓,盡量用較短的命名。比如:大家較熟悉的詞匙奴,就建議用縮寫堆巧;對于作用域比較小的變量(如函數(shù)內(nèi)臨時變量),可以用相對短的命名
  2. 利用上下文簡化命名泼菌。如:類名為 User谍肤,那么里面的屬性就可以直接使用 name,而沒有必要再使用 username
  3. 命名要可讀可搜索哗伯』拇В可讀指的是不要用大家都看不懂的單詞來命名;可搜索指的是通過在寫代碼的時候笋颤,方便地聯(lián)想出對應(yīng)的函數(shù)乳附,如:通過 get 就能找到獲取當(dāng)前對象中的所有函數(shù)内地,而不能說有的地方用的 acquire,這就要求大家在命名時赋除,最好能符合項目的命名習(xí)慣
  4. 對于不同作用域的命名阱缓,我們可以適當(dāng)選擇不同的長度,作用域小的變量举农,可以適當(dāng)選擇一些短的命名方式

9.2 注釋

類和函數(shù)一定要寫注釋荆针,而且需要盡量詳細(xì)一些,而函數(shù)內(nèi)部的注釋相對少一些颁糟,一般要通過好的命名航背、提煉函數(shù)、解釋性變量棱貌、總結(jié)性注釋來提高代碼的可讀性玖媚。

9.3 代碼風(fēng)格

  1. 對于函數(shù)的代碼行數(shù),一般不要超過一屏幕垂直高度婚脱,也就是讓一個函數(shù)的代碼完整地顯示在屏幕上今魔。對于類的代碼行數(shù)有一個間接的評估標(biāo)準(zhǔn),那就是障贸,實現(xiàn)功能不知道要用某個函數(shù)了错森,想用哪個函數(shù)半天也沒有找到,只用一個小功能篮洁,而需要引入整個類的時候涩维,說明類的行數(shù)過多了
  2. 一行代碼的最長不要超過一屏幕的寬度,如果需要通過鼠標(biāo)才能查看一行的全部代碼袁波,顯然不利用代碼的閱讀
  3. 善用代碼行分割單元塊瓦阐。如果邏輯上可以將函數(shù)內(nèi)部的實現(xiàn)分為相對獨立的代碼塊,而代碼塊又不太需要抽成單獨方法的時候锋叨,可以用空行來將其分割垄分。以外,還可以在類的成員變量和函數(shù)之間娃磺,靜態(tài)成員變量和成員變量之間薄湿、各函數(shù)之間、各成員變量之間通過空行來進(jìn)行分割
  4. 類中函數(shù)和變量的排列順序偷卧。靜態(tài)成員變量 -> 成員變量 -> 靜態(tài)方法 -> 普通方法豺瘤,同時,成員變量之間或者方法之間听诸,按照作用域坐求,先寫作用域大的,如:public 變量或方法

9.4 編程技巧

  1. 對于較復(fù)雜的邏輯晌梨,利用模塊化和抽象思維桥嗤,把代碼分割成更小單元塊
  2. 避免函數(shù)參數(shù)過多须妻。函數(shù)參數(shù)大于 5 個左右的時候,就需要考慮是否需要將函數(shù)拆分成多個函數(shù)泛领;或者使用對象來替代普通的參數(shù)傳遞
  3. 勿用函數(shù)參數(shù)來控制邏輯荒吏。不要使用 boolean 參數(shù)來控制函數(shù)的執(zhí)行邏輯,而是通過分拆為兩個函數(shù)來實現(xiàn)
  4. 函數(shù)設(shè)計盡可能職責(zé)單一
  5. 移除過深的代碼嵌套層次渊鞋。一般建議嵌套層次不超過 2 層
  6. 善于使用解釋型變量來提高代碼的可讀性绰更。如:常量代碼魔法數(shù);

說明

此文是根據(jù)王爭設(shè)計模式之美相關(guān)專欄內(nèi)容整理而來锡宋,非原創(chuàng)儡湾。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市执俩,隨后出現(xiàn)的幾起案子徐钠,更是在濱河造成了極大的恐慌,老刑警劉巖奠滑,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丹皱,死亡現(xiàn)場離奇詭異妒穴,居然都是意外死亡宋税,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門讼油,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杰赛,“玉大人,你說我怎么就攤上這事矮台》ν停” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵瘦赫,是天一觀的道長辰晕。 經(jīng)常有香客問我,道長确虱,這世上最難降的妖魔是什么含友? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮校辩,結(jié)果婚禮上窘问,老公的妹妹穿的比我還像新娘。我一直安慰自己宜咒,他們只是感情好惠赫,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著故黑,像睡著了一般儿咱。 火紅的嫁衣襯著肌膚如雪庭砍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天混埠,我揣著相機(jī)與錄音逗威,去河邊找鬼。 笑死岔冀,一個胖子當(dāng)著我的面吹牛凯旭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播使套,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼罐呼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了侦高?” 一聲冷哼從身側(cè)響起嫉柴,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎奉呛,沒想到半個月后计螺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡瞧壮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年登馒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咆槽。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡陈轿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出秦忿,到底是詐尸還是另有隱情麦射,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布灯谣,位于F島的核電站潜秋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏胎许。R本人自食惡果不足惜峻呛,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望呐萨。 院中可真熱鬧杀饵,春花似錦、人聲如沸谬擦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惨远。三九已至谜悟,卻和暖如春话肖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背葡幸。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工最筒, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蔚叨。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓床蜘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蔑水。 傳聞我的和親對象是個殘疾皇子邢锯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353