《代碼整潔之道》精讀與演繹】之四 優秀代碼的格式準則

2020-11-22 電子產品世界

  這篇文章將與大家一起聊一聊,書寫代碼過程中一些良好的格式規範。

本文引用地址:http://www.eepw.com.cn/article/201609/296599.htm

  一、引言

  以下引言的內容,有必要伴隨這個系列的每一次更新,這次也不例外。

  《代碼整潔之道》這本書提出了一個觀點:代碼質量與其整潔度成正比,乾淨的代碼,既在質量上可靠,也為後期維護、升級奠定了良好基礎。書中介紹的規則均來自作者多年的實踐經驗,涵蓋從命名到重構的多個編程方面,雖為一「家」之言,然誠有可資借鑑的價值。

  但我們知道,很多時候,理想很豐滿,現實很骨感,也知道人在江湖,身不由己。因為項目的緊迫性,需求的多樣性,我們無法時時刻刻都寫出整潔的代碼,保持自己輸出的都是高質量、優雅的代碼。

  但若我們理解了代碼整潔之道的精髓,我們會知道怎樣讓自己的代碼更加優雅、整潔、易讀、易擴展,知道真正整潔的代碼應該是怎麼樣的,也許就會漸漸養成持續輸出整潔代碼的習慣。

  而且或許你會發現,若你一直保持輸出整潔代碼的習慣,長期來看,會讓你的整體效率和代碼質量大大提升。

  二、本文涉及知識點思維導圖

  國際慣例,先放出這篇文章所涉及內容知識點的一張思維導圖,就開始正文。大家若是疲於閱讀文章正文,直接看這張圖,也是可以Get到本文的主要知識點的大概。

  

 

  三、優秀代碼的書寫格式準則

  1 像報紙一樣一目了然

  想想那些閱讀量巨大的報紙文章。你從上到下閱讀。在頂部,你希望有個頭條,告訴你故事主題,好讓你決定是否要讀下去。第一段是整個故事的大綱,給出粗線條概述,但隱藏了故事細節。接著讀下去,細節漸次增加,直至你了解所有的日期、名字、引語、說話及其他細節。

  優秀的源文件也要像報紙文章一樣。名稱應當簡單並且一目了然,名稱本身應該足夠告訴我們是否在正確的模塊中。源文件最頂部應該給出高層次概念和算法。細節應該往下漸次展開,直至找到源文件中最底層的函數和細節。

  2 恰如其分的注釋

  帶有少量注釋的整潔而有力的代碼,比帶有大量注釋的零碎而複雜的代碼更加優秀。

  我們知道,注釋是為代碼服務的,注釋的存在大多數原因是為了代碼更加易讀,但注釋並不能美化糟糕的代碼。

  另外,注意一點。注釋存在的時間越久,就會離其所描述的代碼的意義越遠,越來越變得全然錯誤,因為大多數程式設計師們不能堅持(或者因為忘了)去維護注釋。

  當然,教學性質的代碼,多半是注釋越詳細越好。

  3 合適的單文件行數

  儘可能用幾百行以內的單文件來構造出出色的系統,因為短文件通常比長文件更易於理解。當然,和之前的一些準則一樣,只是提供大方向,並非不可違背。

  例如,《代碼整潔之道》第五章中提到的FitNess系統,就是由大多數為200行、最長500行的單個文件來構造出總長約5萬行的出色系統。

  4 合理地利用空白行

  古詩中有留白,代碼的書寫中也要有適當的留白,也就是空白行。

  在每個命名空間、類、函數之間,都需用空白行隔開(應該大家在學編程之初,就早有遵守)。這條極其簡單的規則極大地影響到了代碼的視覺外觀。每個空白行都是一條線索,標識出新的獨立概念。

  其實,在往下讀代碼時,你會發現你的目光總停留於空白行之後的那一行。用空白行隔開每個命名空間、類、函數,代碼的可讀性會大大提升。

  5 讓緊密相關的代碼相互靠近

  如果說空白行隔開了概念,那麼靠近的代碼行則暗示了他們之間的緊密聯繫。所以,緊密相關的代碼應該相互靠近。

  舉個反例(代碼段1):

  [csharp] view plain copy print?

  public class ReporterConfig

  {

  /**

  * The class name of the reporter listener

  */

  private String m_className;

  /**

  * The properties of the reporter listener

  */

  private Listm_properties = new ArrayList();

  public void addProperty(Property property)

  {

  m_properties.add(property);

  }

  }

  再看個正面示例(代碼段2):

  [cpp] view plain copy print?

  public class ReporterConfig

  {

  private String m_className;

  private Listm_properties = new ArrayList();

  public void addProperty(Property property)

  {

  m_properties.add(property);

  }

  }

  以上這段正面示例(代碼段2)比反例(代碼段1)中的代碼好太多,它正好一覽無遺,一眼就能看這個是有兩個變量和一個方法的類。而再看看反例,注釋簡直畫蛇添足,隔斷了兩個實體變量間的聯繫,我們不得不移動頭部和眼球,才能獲得相同的理解度。

  6 基於關聯的代碼分布

  關係密切的概念應該相互靠近。對於那些關係密切、放置於同一源文件中的概念,他們之間的區隔應該成為對相互的易懂度有多重要的衡量標準。應該避免迫使讀者在源文件和類中跳來跳去。變量的聲明應儘可能靠近其使用位置。

  對於大多數短函數,函數中的本地變量應當在函數的頂部出現。例如如下代碼中的is變量的聲明:

  [cpp] view plain copy print?

  private static void readPreferences()

  {

  InputStream is= null;

  try

  {

  is= new FileInputStream(getPreferencesFile());

  setPreferences(new Properties(getPreferences()));

  getPreferences().load(is);

  }

  catch (IOException e)

  {

  DoSomeThing();

  }

  }

  而循環中的控制變量應該總在循環語句中聲明,例如如下代碼中each變量的聲明:

  [cpp] view plain copy print?

  public int countTestCases()

  {

  int count = 0;

  for (Test each : tests)

  count += each.countTestCases();

  return count;

  }

  在某些較長的函數中,變量也可能在某代碼塊的頂部,或在循環之前聲明。例如如下代碼中tr變量的聲明:

  [cpp] view plain copy print?

  ...

  for (XmlTest test : m_suite.getTests())

  {

  TestRunner tr = m_runnerFactory.newTestRunner(this, test);

  tr.addListener(m_textReporter);

  m_testRunners.add(tr);

  invoker = tr.getInvoker();

  for (ITestNGMethod m : tr.getBeforeSuiteMethods())

  {

  beforeSuiteMethods.put(m.getMethod(), m);

  }

  for (ITestNGMethod m : tr.getAfterSuiteMethods())

  {

  afterSuiteMethods.put(m.getMethod(), m);

  }

  }

  ...

  另外,實體變量應當在類的頂部聲明(也有一些流派喜歡將實體變量放到類的底部)。

  若某個函數調用了另一個,就應該把它們放到一起,而且調用者應該儘量放到被調用者上面。這樣,程序就有自然的順序。若堅定地遵守這條約定,讀者將能夠確信函數聲明總會在其調用後很快出現。

  概念相關的代碼應該放到一起。相關性越強,則彼此之間的距離就該越短。

  這一節的要點整理一下,大致就是:

  變量的聲明應儘可能靠近其使用位置。

  循環中的控制變量應該在循環語句中聲明。

  短函數中的本地變量應當在函數的頂部聲明。

  而對於某些長函數,變量也可以在某代碼塊的頂部,或在循環之前聲明。

  實體變量應當在類的頂部聲明。

  若某個函數調用了另一個,就應該把它們放到一起,而且調用者應該儘量放到被調用者上面。

  概念相關的代碼應該放到一起。相關性越強,則彼此之間的距離就該越短。

  7 團隊遵從同一套代碼規範

  一個好的團隊應當約定與遵從一套代碼規範,並且每個成員都應當採用此風格。我們希望一個項目中的代碼擁有相似甚至相同的風格,像默契無間的團隊所完成的藝術品,而不是像一大票意見相左的個人所堆砌起來的殘次品。

  定製一套編碼與格式風格不需要太多時間,但對整個團隊代碼風格統一性的提升,卻是立竿見影的。

  記住,好的軟體系統是由一系列風格一致的代碼文件組成的。儘量不要用各種不同的風格來構成一個項目的各個部分,這樣會增加項目本身的複雜度與混亂程度。

  四、範例代碼

  和上篇文章一樣,有必要貼出一段書中推崇的整潔代碼作為本次代碼書寫格式的範例。書中的這段代碼採用java語言,但絲毫不影響使用C++和C#的朋友們閱讀。

  [cpp] view plain copy print?

  public class CodeAnalyzer implements JavaFileAnalysis

  {

  private int lineCount;

  private int maxLineWidth;

  private int widestLineNumber;

  private LineWidthHistogram lineWidthHistogram;

  private int totalChars;

  public CodeAnalyzer()

  {

  lineWidthHistogram = new LineWidthHistogram();

  }

  public static List findJavaFiles(File parentDirectory)

  {

  List files = new ArrayList();

  findJavaFiles(parentDirectory, files);

  return files;

  }

  private static void findJavaFiles(File parentDirectory, List files)

  {

  for (File file : parentDirectory.listFiles())

  {

  if (file.getName().endsWith(".java"))

  files.add(file);

  else if (file.isDirectory())

  findJavaFiles(file, files);

  }

  }

  public void analyzeFile(File javaFile) throws Exception

  {

  BufferedReader br = new BufferedReader(new FileReader(javaFile));

  String line;

  while ((line = br.readLine()) != null)

  measureLine(line);

  }

  private void measureLine(String line)

  {

  lineCount++;

  int lineSize = line.length();

  totalChars += lineSize;

  lineWidthHistogram.addLine(lineSize, lineCount);

  recordWidestLine(lineSize);

  }

  private void recordWidestLine(int lineSize)

  {

  if (lineSize > maxLineWidth)

  {

  maxLineWidth = lineSize;

  widestLineNumber = lineCount;

  }

  }

  public int getLineCount()

  {

  return lineCount;

  }

  public int getMaxLineWidth()

  {

  return maxLineWidth;

  }

  public int getWidestLineNumber()

  {

  return widestLineNumber;

  }

  public LineWidthHistogram getLineWidthHistogram()

  {

  return lineWidthHistogram;

  }

  public double getMeanLineWidth()

  {

  return (double)totalChars / lineCount;

  }

  public int getMedianLineWidth()

  {

  Integer[] sortedWidths = getSortedWidths();

  int cumulativeLineCount = 0;

  for (int width : sortedWidths)

  {

  cumulativeLineCount += lineCountForWidth(width);

  if (cumulativeLineCount > lineCount / 2)

  return width;

  }

  throw new Error("Cannot get here");

  }

  private int lineCountForWidth(int width)

  {

  return lineWidthHistogram.getLinesforWidth(width).size();

  }

  private Integer[] getSortedWidths()

  {

  Set widths = lineWidthHistogram.getWidths();

  Integer[] sortedWidths = (widths.toArray(new Integer[0]));

  Arrays.sort(sortedWidths);

  return sortedWidths;

  }

  }

  五、小結:讓代碼不僅僅是能工作

  代碼的格式關乎溝通,而溝通是專業開發者的頭等大事,所以良好代碼的格式至關重要。

  或許之前我們認為「讓代碼能工作」才是專業開發者的頭等大事。然而,《代碼整潔之道》這本書,希望我們能拋棄這個觀點。

  讓代碼能工作確實是代碼存在的首要意義,但作為一名有追求的程式設計師,請你想一想,今天你編寫的功能,極可能在下一版中被修改,但代碼的可讀性卻會對以後可能發生的修改行為產生深遠影響。原始代碼修改之後很久,其代碼風格和可讀性仍會影響到可維護性和擴展性。即便代碼已不復存在,你的風格和律條仍影響深遠。

  「當有人在閱讀我們的代碼時,我們希望他們能為其整潔性、一致性和優秀的細節處理而震驚。我們希望他們高高揚起眉毛,一路看下去,希望他們感受能到那些為之勞作的專業人士們的優秀職業素養。但若他們看到的只是一堆由酒醉的水手寫出的鬼畫符,那他們多半會得出結論——這個項目的其他部分應該也是混亂不堪的。」

  所以,各位,在開發過程中請不要僅僅是停留在「讓代碼可以工作」的層面,而更要注重自身輸出代碼的可維護性與擴展性。請做一個更有追求的程式設計師。

  六、本文涉及知識點提煉整理

  整潔代碼的書寫格式,可以遵從如下幾個原則:

  第一原則:像報紙一樣一目了然。優秀的源文件也要像報紙文章一樣,名稱應當簡單並且一目了然,名稱本身應該足夠告訴我們是否在正確的模塊中。源文件最頂部應該給出高層次概念和算法。細節應該往下漸次展開,直至找到源文件中最底層的函數和細節。

  第二原則:恰如其分的注釋。帶有少量注釋的整潔而有力的代碼,比帶有大量注釋的零碎而複雜的代碼更加優秀。

  第三原則:合適的單文件行數。儘可能用幾百行以內的單文件來構造出出色的系統,因為短文件通常比長文件更易於理解。

  第四原則:合理地利用空白行。在每個命名空間、類、函數之間,都需用空白行隔開。

  第五原則:讓緊密相關的代碼相互靠近。靠近的代碼行暗示著他們之間的緊密聯繫。所以,緊密相關的代碼應該相互靠近。

  第六原則:基於關聯的代碼分布。

  變量的聲明應儘可能靠近其使用位置。

  循環中的控制變量應該在循環語句中聲明。

  短函數中的本地變量應當在函數的頂部聲明。

  對於某些長函數,變量也可以在某代碼塊的頂部,或在循環之前聲明。

  實體變量應當在類的頂部聲明。

  若某個函數調用了另一個,就應該把它們放到一起,而且調用者應該儘量放到被調用者上面。

  概念相關的代碼應該放到一起。相關性越強,則彼此之間的距離就該越短。

  第七原則:團隊遵從同一套代碼規範。一個好的團隊應當約定與遵從一套代碼規範,並且每個成員都應當採用此風格。

  本文就此結束。

  下篇文章,我們將繼續《代碼整潔之道》的精讀與演繹,探討更多的內容。

  Best Wish~

相關焦點

  • 經驗之談:代碼該怎樣寫才能幹淨整潔
    能把代碼寫出來是一回事,但是寫出整潔、可讀的代碼又是另一回事。然而,什麼是「乾淨的代碼」呢?怎麼才能寫出「乾淨的代碼」?為了解答這些問題,本文作者寫了一份針對初級開發者的乾淨代碼指南。不妨想像一下,你正在閱讀一篇文章。文章開頭有一個段落簡要概述了文章的內容。文中還有一些標題,每個小標題會引出幾個段落。
  • 程式設計師如何寫出高質量的代碼程序
    從配置文件中讀取變量很多人喜歡在程序中通過注釋來修改變量值,這樣的做法非常不對,首先不說無用的注釋影響了代碼的整潔,就通過修改代碼來修改變量的值就是不優雅的。一個優秀的程序,一定是從配置文件中讀取所需要的變量的,而修改配置文件對於一個人來說遠遠比去原始碼中修改變量值要方便得多得多。
  • Excel自定義格式代碼-顏色及大小寫類代碼符號及其作用
    今天我們來介紹顏色及大小寫類格式代碼的符號及其作用。要注意符號需要在英文狀態下輸入,另外TEXT函數不支持顏色代碼。1.示例:原始輸入:123自定義格式代碼:[洋紅]結果顯示為:123(洋紅色)2.[顏色n]n在1~56之間,分別對應著56種顏色,如下圖。
  • 論文繪圖神器:一行代碼繪製不同期刊格式圖表,哈佛博士後開源
    賈浩楠 發自 凹非寺量子位 報導 | 公眾號 QbitAI「一篇論文投多個期刊,每個期刊對圖表格式要求不一,同一組數據要用多種工具分別繪圖。」不光是你,哈佛大學天文研究所的博士後,也不堪忍受論文重複繪圖之苦。
  • 精選20道Java代碼筆試題
    1、運算符優先級問題,下面代碼的結果是多少?2、運算符問題,下面代碼分別輸出什麼?null是任何引用類型的初始值,String和Object的初始值都是null,但是null會優先匹配引用類型參數為String的方法,因此這道題答案是string。假設這道題中還有其他同是引用類型的重載方法呢?
  • DSM、DSA:原始碼防洩密之爭
    首頁 > 傳媒 > 關鍵詞 > 原始碼最新資訊 > 正文 DSM、DSA:原始碼防洩密之爭
  • 一行代碼安裝,TPU也能運行PyTorch,修改少量代碼即可快速移植
    現在福利來了,一個叫做Pytorch Lightning的項目,可以讓你幾乎修改代碼的情況下用上TPU。Pytorch Lightning已經上傳到PyPI,因此只需一行代碼就能安裝這個軟體。幾乎無需修改代碼首先讓我們來看一個MNIST圖像分類網絡的搭建,PyTorch的原始代碼和修改後的PyTorch Lightning代碼幾乎無異。我們只需將nn.Module替換為pl.LightningModule即可。作者表示,相比切換框架,用這種方法重構原來的代碼只需數小時的時間。
  • 開發者需要的 6 款代碼比較工具
    在程序開發的過程中,程式設計師會經常對原始碼以及庫文件進行代碼對比,在這篇文章裡我們向大家介紹六款程式設計師常用的代碼比較工具WinMergeWinMerge是一款運行於Windows系統下的文件比較和合併工具,使用它可以非常方便地比較多個文檔內容,適合程式設計師或者經常需要撰寫文稿的朋友使用
  • 用歐拉方程模擬無粘性染料之代碼實現
    上面那個例子寫成C++代碼如下,貼到main函數裡即可運行。下面這段代碼實際上是錯的,並不能正確求得壓力,因為沒考慮邊界情況。但是看看下面的代碼至少能讓你對大體求解方法是怎樣的有個直觀的印象。 unity上的代碼實現首先看看是附著到攝像機上的代碼,與之前c++代碼中的步驟大體相同。
  • 避免這些代碼壞味道,努力做一名優秀的程式設計師
    Martin Fowler:任何一個傻瓜都能寫出計算機可以理解的代碼。唯有寫出人類容易理解的代碼,才是優秀的程式設計師。大家閉著眼睛想一下什麼是好代碼?也許你的腦海中漂浮著一堆詞:乾淨、整潔、命名規範、注釋合理、高內聚低耦合……人人都想寫好代碼,因為看好代碼就如同看一位五官端正的女子,心情愉悅、舒暢,而看糟糕的代碼就如同看見腐爛的食物,聞起來也有一股壞味道。
  • Python 之父 Guido van Rossum 談如何以代碼的方式思考
    科技企業的代碼庫以數百萬行為計,當你在考慮用代碼構建一個系統時,你會反覆思考不同功能之間的複雜關係,以及代碼的編寫和結構會對系統造成什麼影響等,但你需要解決的問題和具有其固有的邏輯複雜性,不能簡單地就說來「簡化」這個系統。身為一名程式設計師重要的並不僅僅是電子,如果你沒有使用代碼來描述你的想法,你在行業內是不會生存太久的。
  • 詳解一種端遊遊戲代碼保護方案
    一般而言,反外掛由兩個部分組成,即靜態代碼保護與動態運行時對抗。本文將聚焦靜態代碼保護,從實際的保護效果出發,先闡述通用的PE代碼保護在易盾端遊反外掛代碼保護中的應用,後介紹面向遊戲引擎的代碼保護策略,並在此基礎上,探索一種通用的遊戲邏輯代碼保護方案。
  • 推薦5 款最佳最牛代碼比較工具!新手上路,老手挺住!
    一:Beyond Compare 推薦:★★★★★ Beyond Compare可以很方便地對比出兩份原始碼文件之間的不同之處,相差的每一個字節用顏色加以表示,查看方便,支持多種規則對比。
  • Python代碼轉Latex公式,這個開源庫用一行代碼幫你搞定
    機器之心報導編輯:小舟你的代碼中有數學公式嗎?數學是數據科學和機器學習的重要基礎,數學運算的結果對於機器學習項目而言是至關重要的。在編寫代碼時,我們常常需要定義數學公式的計算形式。像 S=r^2 這樣簡單的數學公式,大概不會出現拼寫錯誤。但如果是下面這樣的公式呢?
  • 航空公司代碼及呼號
    航空公司的三字碼:是ICAO國際民航組織為世界上所有航空器公司訂製的識別代碼,由三個英文字母組成,該代碼少在公眾使用,主要用於空中交通管理部門。航空公司也使用二字代碼,是國際航空器運輸協會,對航空公司進行編號,通常由兩位字母或者兩位字母及數字組成,一般用在航空公司之間的飛行動態格式報文或相關票務上。
  • 利用VBA代碼將文本轉換為數組函數
    看全球股市震蕩,市場信心不足,歐美股市熔斷頻發,後疫情時代將終將演繹一場戰勝蕭條的戰役。無論怎樣,我們一定要堅信,疫情終將會過去,曙光一定會到來。後疫情時代將會是一個全新的世界,很多理念都將被打破,大多數人不會再享受體制內的保護,對於我們每個人,要儘可能多的學習有用的知識,為自己充電。在今後更加嚴峻的存量殘殺世界中,為自己的生存進行知識的儲備,特別是新知識的儲備。
  • 簡單實用的數據清洗代碼
    自此,我開始組織和編譯一些我認為適用於其他常見場景的數據清理代碼 - 我用於數據清理的小工具箱。由於此處的常見方案跨越不同類型的數據集,因此本文著重於展示和解釋代碼的用途,以便您可以輕鬆地進行調用。在本文的最後,我希望你能找到有用的代碼,這將使你的數據清理過程更加快速有效。讓我們開始吧!
  • 利用VBA「積木」過程代碼,實現個人小型辦公自動化之224
    大家好,今日繼續和大家分享VBA編程中常用的常用「積木」過程代碼。這些內容大多是我的經驗的記錄,來源於我多年的經驗。今日分享的是NO.223-NO.224,內容是:NO. 223:計算數組的上標下標及元素的個數值NO. 224:數組函數JOIN的作用VBA過程代碼223:計算數組的上標下標及元素的個數值Sub Mynz ()Dim arr(10 To 50)Dim brr(1 To 10, 1 To 100)MsgBox
  • 宇宙之謎:反物質失蹤、黑色的太空及DNA代碼
    一,反物質失蹤之謎 138億年前的一瞬,「砰」的一聲,宇宙從「奇點」爆炸,物質粒子與反物質粒子同時誕生又逐對湮滅。 三,DNA與代碼。 DNA是上帝的代碼,生物學將是下一個偉大的計算平臺,DNA是代碼,Crispr是程式語言。
  • 「記」詳解C語言之格式
    C語言有以下優點:代碼量小速度快功能強大C語言的編程開發有以下幾個:編程軟體我們用C語言寫代碼時,經常用的是以下這種格式:# include<stdio.h>int main(void){return 0;}我們知道在以上那種格式中的大括號(也叫做花括號)「{}」內寫進我們的代碼可以讓我們的程序正確運行,可是很多人可能不知道為什麼要這麼寫?