代碼之美——像寫做同樣去coding

做爲程序員,咱們或許經常會被問到:你都學過什麼語言呢?你最擅長的是哪一門語言?是的,一門語言。java

這裏所提到的語言並不是咱們的母語漢語,也不是英語亦或其餘任何一種用於交流平常工做生活的語言。而是指編程過程當中,連通人與機器、人與人之間的一種表達方式。讓機器讀懂代碼很簡單,只需註明所用代碼的語言規則就好,畢竟機器那麼聰明 :)可是若是想要讓其餘人看懂,就不能這樣簡單粗暴了。人是感性與理性結合的動物,優雅「風趣」的表達可以讓對方更快、更輕鬆的讀懂你的代碼。程序員

既然都是表達內容,那麼爲何不用寫文章的方式來寫代碼呢?文章是人們平常用於交流表達的一種方式,那麼咱們是否能夠吸取文章的優點來用在寫代碼上呢?編程

讓句子連在一塊兒組成段落
咱們能夠試着把方法抽象成文章裏的一句話,方法內緊接着調用的另外一個方法,就好像是第一句話還須要第二句話去完善同樣。因此咱們應該把句子2放在句子 1 後面,也就是說咱們能夠把被調用的方法放在調用方法下面。架構

同理,一個方法內部兩個相鄰方法的調用前後順序就像是文章裏兩個相鄰句子的前後順序同樣。因此咱們也應把這種順序做爲方法上下排列的順序。ide

那麼若是咱們不遵循這種規則會怎麼樣呢?this

private void preparePizza(Pizza pizza) {
  getFlour();
}

private void boxPizza(Pizza pizza) {
...
}

public Pizza orderPizza(String type) {
  Pizza pizza = getBasePizza(type);
  preparePizza(pizza);
  boxPizza(pizza);
  return pizza;
}

private Flour getFlour() {
...
}

private Pizza getBasePizza(String type) {
...
}

上面這段代碼方法排序是隨意的,咱們沒法直觀的看到方法的執行順序。就好像是:「再而後我去吃早飯;而後我去洗漱;我早上七點起牀」,混亂的順序增長了咱們理解代碼的困難度。spa

若是咱們遵循這兩種規則來排序方法,那就以下面這樣:插件

public Pizza orderPizza(String type) {
  Pizza pizza;
  pizza = getPizza(type);

  preparePizza();
  boxPizza();
  return pizza;
}

private Pizza getPizza(String type) {
...
}

private void preparePizza() {
  getFlour();
}

private Flour getFlour() {
...
}

private void boxPizza() {
...
}
</pre>

當咱們閱讀這段代碼時,會以爲這是一個總體,只須要向讀文章同樣,上下滑動閱讀便可。設計

有的人可能會說,經過快捷鍵同樣能夠定位到下一個方法。可是快捷鍵僅適用於邏輯簡單的狀況,在複雜的邏輯中來回定位所產生的上下跳躍會讓人以爲很是難受,這也是咱們應當竭力避免的。code

定義小範圍章節目錄

一本書或一篇長文,通常都會有章節目錄。就好像一個類中有幾個提供給外界調用的public方法,這可使咱們有很好的全局觀。因此咱們應該把類中一些提供主要功能的對外方法放到一塊兒,這些方法要以功能相近來集聚。

以下:

@Override
public ResultT getAResult(KeyT keySearch) {
  ...
}

@Override
public ResultT getAResultUntilTimeout(KeyT keyT, long timeout, TimeUnit timeUnit) throws TimeoutException {
  ...
}

@Override
public List<ResultT> getResultsUntilOneTimeout(KeyT keyT, long timeout, TimeUnit unit) {
  ...
}

@Override
public List<ResultT> getResultsUntilTimeout(KeyT keyT, long timeout, TimeUnit unit) {
  ...
}

@Override
public List<ResultT> getResultsUntilEnoughOrTimeout(KeyT keyT, int expectNum, long timeout, TimeUnit unit) {
  ...
}

@Override
public List<ResultT> getResultsUntilEnoughOrOneTimeout(KeyT keyT, int expectNum, long timeout, TimeUnit unit) {
  ...
}

@Override
public List<ResultT> getResultsUntilEnough(KeyT keyT, int expectNum) throws TimeoutException {
  ...
}
</pre>

這是我寫的一個search-framework中的部分代碼,這些方法都是相近的,因此把它們放到一塊兒。另外咱們要小範圍的集聚,即把類似的開放式方法放在一塊兒,這些方法的下面就是內部調用的方法,繼續遵循「讓句子連在一塊兒組成段落」的理念。

其實有一種更好的辦法,就是能夠用一種插件讓IDEA自動生成一種目錄式方法。這種方法只包含基本信息,沒有內部實現,而且咱們能夠點擊目錄進入方法的準確位置(準確位置的方法排序遵循段落式描述法)。至於如何讓IDEA知道哪些方法應該生成目錄式方法,咱們或許能夠經過某種註解去定義。

那麼它看起來就好像下面這樣:

public class ConcurrentEntirelySearch<KeyT, ResultT, PathT> implements EntirelySearch<KeyT, ResultT> {
  private static final long MAX_WAIT_MILLISECOND = 1000 * 60 * 2;

  private final List<PathT> rootCanBeSearch;
  private final ConcurrentEntirelyOpenSearch<KeyT, ResultT, PathT> openSearch;

  public ConcurrentEntirelySearch(List<PathT> rootCanBeSearch, SearchModel searchModel) {
    this.rootCanBeSearch = rootCanBeSearch;
    this.openSearch = new ConcurrentEntirelyOpenSearch<>(searchModel);
  }

/** 目錄(如何展現細節待設計)*/
  @Override
--- public ResultT getAResult(KeyT keySearch) {...}

  @Override
--- public ResultT getAResultUntilTimeout(KeyT keyT, long timeout, TimeUnit timeUnit) throws TimeoutException {...}

  @Override
--- public List<ResultT> getResultsUntilOneTimeout(KeyT keyT, long timeout, TimeUnit unit) {...}

  @Override
--- public List<ResultT> getResultsUntilTimeout(KeyT keyT, long timeout, TimeUnit unit) {...}
/** 目錄完(虛擬內容,可點擊跳轉至方法)------------------- */

  @Override
  public ResultT getAResult(KeyT keySearch) { // 此爲真實方法,非目錄
    methodA();      //方法排序遵循上述的 段落式描述法
    methodB();
  }

  private void methodA() {
  ...
  }

  private void methodB() {
  ...
  }
  //下同,方法內調用的方法略
  @Override
  public ResultT getAResultUntilTimeout(KeyT keyT, long timeout, TimeUnit timeUnit) throws TimeoutException {
    ...
  }

  @Override
  public List<ResultT> getResultsUntilOneTimeout(KeyT keyT, long timeout, TimeUnit unit) {
    ...
  }   @Override
  public List<ResultT> getResultsUntilTimeout(KeyT keyT, long timeout, TimeUnit unit) {
    ...
  }
}

這些目錄我認爲應該放在構造方法的下面,這樣看起來更加有條理。

寫文章時不要讓每一行過長

相信沒有人願意去看由一行行長文所組成的段落,長度適中的段落可以讓讀者在跳行時有一個休息,也給大腦一個輕微的緩衝,這樣的閱讀溫馨感會高不少。因此咱們要善於利用這個度,不要讓代碼過長,可是有時候也能夠利用這個度去作inline,只要不超過那個限度就ok。

 

這個思想是在一次ThoughWorks的活動中受到的啓發,inline是很好,可是它不能過分,只要咱們遵循「寫文章時不要讓每一行過長」的理念就ok。讓讀者得以take a breath。

就好像下面此次重構同樣:

//重構前
@Test
public void should_return_1B_given_1000000_length() {
  Gold gold = new Gold(1000000);
  String length = gold.getLength();
  assertEquals("1B", length);
}
//重構後
@Test
public void should_return_1B_given_1000000_length() {
  assertEquals("1B", new Gold(1000000).getLength());
}

上面這個例子就利用了這種理念,在讀者讀一行代碼時,能接受的最多字符是有限的,過長就會產生疲倦感、厭惡感。

下面來看一個反例:

//重構前
int previousNumber = getNumberByUnit(lastIndex);
String target = numberString.substring(0, lastIndex);
compute(Long.parseLong(target), previousNumber);
//重構後
int previousNumber = getNumberByUnit(lastIndex);
compute(Long.parseLong(numberString.substring(0, lastIndex)), previousNumber);

這裏有必要解釋一下「度」的概念,我認爲度不該該以每一行能容納的字符數來衡量。而是要以 該行內變量或方法命名的長度、該行內嵌套調用的方法數量、該行內調用方法的參數數量 這三點綜合去考慮。

「某一行命名比較長」 、 「某一行嵌套調用的方法比較多」 和 「某一行方法的參數比較多」 所能承受的最大字符數是不同的。好比:讀者能接受的「命名比較長」的最大長度跟所能接受的 「調用方法多的」 最大長度所能容納的字符數確定不同,由於命名就算再長點也還像是一句話,咱們也還算能夠理解,而調用的方法逐漸變多那理解的複雜度就會幾何增加。

總結

如文載道,要想讓本身的代碼發揮更大的影響,就必定要花時間去琢磨怎麼把它寫的更易讀。咱們應堅持寫「笨」代碼的思想,若是代碼能像文章那樣有條理,有規律可循,那無疑能夠加強代碼的可維護性。這樣的代碼閱讀起來也會讓人更加溫馨 歡迎加入java中高端架構師交流羣:603619042 面向1-5年java人員 幫助突破划水瓶頸,提高思惟能力

相關文章
相關標籤/搜索